@mohasinac/appkit 2.7.11 → 2.7.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/dist/_internal/server/jobs/core/assignSpinPrize.js +8 -7
  2. package/dist/_internal/server/jobs/core/bundleStockSync.js +17 -20
  3. package/dist/_internal/server/jobs/core/countersReconcile.js +3 -2
  4. package/dist/_internal/server/jobs/core/onProductStockChange.js +18 -20
  5. package/dist/_internal/server/jobs/core/onProductWrite.js +3 -2
  6. package/dist/_internal/server/jobs/core/prizeRevealClose.js +7 -5
  7. package/dist/_internal/server/jobs/core/prizeRevealExpiry.js +11 -6
  8. package/dist/_internal/server/jobs/core/prizeRevealOpen.js +13 -7
  9. package/dist/_internal/server/jobs/core/prizeRevealReminder.js +9 -4
  10. package/dist/_internal/server/jobs/core/triggerEventRaffle.js +3 -2
  11. package/dist/_internal/shared/features/blog/schema.d.ts +4 -4
  12. package/dist/_internal/shared/features/categories/bundle-copy.d.ts +3 -6
  13. package/dist/_internal/shared/features/categories/bundle-copy.js +3 -6
  14. package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +32 -32
  15. package/dist/_internal/shared/features/events/schema.d.ts +4 -4
  16. package/dist/_internal/shared/features/products/schema.d.ts +20 -20
  17. package/dist/_internal/shared/features/promotions/schema.d.ts +2 -2
  18. package/dist/_internal/shared/features/reviews/schema.d.ts +1 -1
  19. package/dist/constants/field-names.d.ts +824 -0
  20. package/dist/constants/field-names.js +929 -0
  21. package/dist/constants/sort.d.ts +7 -0
  22. package/dist/constants/sort.js +5 -0
  23. package/dist/constants/table-keys.d.ts +48 -0
  24. package/dist/constants/table-keys.js +46 -0
  25. package/dist/features/account/components/AddressesIndexListing.js +9 -6
  26. package/dist/features/admin/components/AdminAllEventEntriesView.js +4 -1
  27. package/dist/features/admin/components/AdminBidsView.js +4 -1
  28. package/dist/features/admin/components/AdminBlogView.js +9 -2
  29. package/dist/features/admin/components/AdminBrandsView.js +9 -2
  30. package/dist/features/admin/components/AdminBundlesView.js +0 -1
  31. package/dist/features/admin/components/AdminCarouselView.js +4 -1
  32. package/dist/features/admin/components/AdminCartsView.js +4 -1
  33. package/dist/features/admin/components/AdminCategoriesView.js +9 -2
  34. package/dist/features/admin/components/AdminContactView.js +4 -1
  35. package/dist/features/admin/components/AdminCouponsView.js +4 -1
  36. package/dist/features/admin/components/AdminFaqsView.js +5 -1
  37. package/dist/features/admin/components/AdminFeaturesView.js +5 -2
  38. package/dist/features/admin/components/AdminHistoryView.js +5 -1
  39. package/dist/features/admin/components/AdminNewsletterView.js +4 -1
  40. package/dist/features/admin/components/AdminNotificationsView.js +4 -1
  41. package/dist/features/admin/components/AdminOrdersView.js +14 -12
  42. package/dist/features/admin/components/AdminPayoutsView.js +4 -1
  43. package/dist/features/admin/components/AdminPrizeDrawsView.js +4 -1
  44. package/dist/features/admin/components/AdminProductsView.js +14 -3
  45. package/dist/features/admin/components/AdminReturnRequestsView.js +4 -1
  46. package/dist/features/admin/components/AdminReviewsView.js +4 -1
  47. package/dist/features/admin/components/AdminScammersView.js +4 -1
  48. package/dist/features/admin/components/AdminSessionsView.js +4 -1
  49. package/dist/features/admin/components/AdminStoreAddressesView.js +5 -1
  50. package/dist/features/admin/components/AdminStoresView.js +10 -3
  51. package/dist/features/admin/components/AdminSublistingCategoriesView.js +4 -1
  52. package/dist/features/admin/components/AdminSupportTicketsView.js +4 -1
  53. package/dist/features/admin/components/AdminTeamView.js +4 -1
  54. package/dist/features/admin/components/AdminUsersView.js +9 -12
  55. package/dist/features/admin/components/AdminViewCards.d.ts +12 -0
  56. package/dist/features/admin/components/AdminViewCards.js +53 -0
  57. package/dist/features/admin/components/AdminWishlistsView.js +4 -1
  58. package/dist/features/auctions/repository/bid.repository.js +1 -6
  59. package/dist/features/auctions/schemas/index.d.ts +4 -4
  60. package/dist/features/blog/actions/blog-actions.d.ts +16 -16
  61. package/dist/features/blog/components/BlogFilters.d.ts +28 -28
  62. package/dist/features/blog/components/BlogFilters.js +18 -15
  63. package/dist/features/blog/components/BlogIndexListing.js +20 -16
  64. package/dist/features/blog/schemas/index.d.ts +10 -10
  65. package/dist/features/categories/components/BundleDetailView.js +0 -1
  66. package/dist/features/categories/components/CategoriesIndexListing.js +25 -21
  67. package/dist/features/categories/components/CategoryBundlesListing.js +2 -4
  68. package/dist/features/categories/schemas/firestore.d.ts +1 -1
  69. package/dist/features/categories/schemas/index.d.ts +2 -2
  70. package/dist/features/events/components/AdminEventsView.js +5 -1
  71. package/dist/features/events/components/EventFilters.d.ts +24 -24
  72. package/dist/features/events/components/EventFilters.js +16 -13
  73. package/dist/features/events/components/EventStatusBadge.js +1 -0
  74. package/dist/features/events/components/EventsIndexListing.js +31 -26
  75. package/dist/features/events/components/EventsListPageView.js +13 -10
  76. package/dist/features/events/schemas/firestore.d.ts +1 -0
  77. package/dist/features/events/schemas/firestore.js +1 -0
  78. package/dist/features/events/schemas/index.d.ts +1 -1
  79. package/dist/features/events/types/index.d.ts +1 -1
  80. package/dist/features/faq/actions/faq-actions.d.ts +10 -10
  81. package/dist/features/faq/schemas/index.d.ts +4 -4
  82. package/dist/features/homepage/components/FeaturedBundlesSection.js +1 -3
  83. package/dist/features/orders/components/OrderFilters.d.ts +20 -20
  84. package/dist/features/orders/components/OrderFilters.js +33 -30
  85. package/dist/features/orders/repository/orders.repository.js +1 -15
  86. package/dist/features/orders/schemas/index.d.ts +2 -2
  87. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +35 -31
  88. package/dist/features/pre-orders/repository/preorders.repository.js +8 -4
  89. package/dist/features/pre-orders/schemas/index.d.ts +3 -3
  90. package/dist/features/products/components/AuctionsIndexListing.js +34 -30
  91. package/dist/features/products/components/BidHistory.d.ts +0 -1
  92. package/dist/features/products/components/BidHistory.js +1 -1
  93. package/dist/features/products/components/PrizeDrawsIndexListing.js +44 -40
  94. package/dist/features/products/components/ProductDetailPageView.js +1 -1
  95. package/dist/features/products/components/ProductFilters.d.ts +20 -20
  96. package/dist/features/products/components/ProductFilters.js +62 -59
  97. package/dist/features/products/components/ProductsIndexListing.js +28 -24
  98. package/dist/features/products/components/ProductsIndexPageView.js +18 -10
  99. package/dist/features/products/hooks/useProducts.js +2 -0
  100. package/dist/features/products/repository/products.repository.js +2 -22
  101. package/dist/features/products/schemas/index.d.ts +28 -28
  102. package/dist/features/products/schemas/product-features.validators.d.ts +12 -12
  103. package/dist/features/products/types/index.d.ts +2 -0
  104. package/dist/features/promotions/components/CouponsIndexListing.js +57 -32
  105. package/dist/features/promotions/schemas/index.d.ts +4 -4
  106. package/dist/features/reviews/components/ReviewDetailShell.js +6 -5
  107. package/dist/features/reviews/components/ReviewFilters.d.ts +16 -16
  108. package/dist/features/reviews/components/ReviewFilters.js +10 -7
  109. package/dist/features/reviews/components/ReviewsIndexListing.js +21 -17
  110. package/dist/features/reviews/components/ReviewsList.d.ts +5 -2
  111. package/dist/features/reviews/components/ReviewsList.js +11 -5
  112. package/dist/features/reviews/schemas/firestore.d.ts +1 -0
  113. package/dist/features/reviews/schemas/index.d.ts +8 -8
  114. package/dist/features/reviews/types/index.d.ts +1 -0
  115. package/dist/features/search/schemas/index.d.ts +2 -2
  116. package/dist/features/seller/components/SellerAuctionsView.js +5 -1
  117. package/dist/features/seller/components/SellerBidsView.js +4 -1
  118. package/dist/features/seller/components/SellerCouponsView.js +4 -1
  119. package/dist/features/seller/components/SellerOffersView.js +5 -1
  120. package/dist/features/seller/components/SellerOrdersView.js +4 -1
  121. package/dist/features/seller/components/SellerPayoutsView.js +5 -1
  122. package/dist/features/seller/components/SellerPreOrdersView.js +4 -1
  123. package/dist/features/seller/components/SellerPrizeDrawsView.js +4 -1
  124. package/dist/features/seller/components/SellerProductsView.js +4 -1
  125. package/dist/features/seller/schemas/index.d.ts +12 -12
  126. package/dist/features/stores/api/route.js +10 -1
  127. package/dist/features/stores/components/StoreReviewsListing.js +1 -1
  128. package/dist/features/stores/components/StoresIndexListing.js +24 -20
  129. package/dist/features/stores/repository/stores.repository.js +8 -5
  130. package/dist/features/stores/schemas/firestore.d.ts +1 -0
  131. package/dist/features/stores/schemas/index.d.ts +4 -4
  132. package/dist/index.d.ts +8 -1
  133. package/dist/index.js +12 -1
  134. package/dist/providers/firebase-client/auth.d.ts +7 -0
  135. package/dist/providers/firebase-client/auth.js +15 -8
  136. package/dist/react/hooks/useUrlTable.js +2 -1
  137. package/dist/seed/bids-seed-data.js +45 -44
  138. package/dist/seed/categories-seed-data.js +41 -40
  139. package/dist/seed/coupons-seed-data.js +41 -40
  140. package/dist/seed/products-auctions-seed-data.js +54 -53
  141. package/dist/seed/products-preorders-seed-data.js +25 -24
  142. package/dist/seed/products-prize-draws-seed-data.js +24 -23
  143. package/dist/seed/products-standard-seed-data.js +294 -293
  144. package/dist/seed/reviews-seed-data.js +61 -60
  145. package/dist/seed/scammers-seed-data.js +3 -2
  146. package/dist/seed/stores-seed-data.js +9 -8
  147. package/dist/seed/support-tickets-seed-data.js +13 -12
  148. package/dist/seed/users-seed-data.js +28 -27
  149. package/dist/tailwind-utilities.css +1 -1
  150. package/dist/utils/sieve-builder.d.ts +41 -0
  151. package/dist/utils/sieve-builder.js +66 -0
  152. package/package.json +2 -2
@@ -4,7 +4,9 @@ import React, { useState, useCallback } from "react";
4
4
  import { X } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
6
  import { usePanelUrlSync } from "../../../react/hooks/use-panel-url-sync";
7
- import { FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, RowActionMenu } from "../../../ui";
7
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
8
+ import { BulkActionBar, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, RowActionMenu } from "../../../ui";
9
+ import { AdminViewCards } from "./AdminViewCards";
8
10
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
9
11
  import { ADMIN_STORE_STATUS_TABS } from "../constants/filter-tabs";
10
12
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
@@ -21,6 +23,7 @@ const SORT_OPTIONS = [
21
23
  const STATUS_OPTIONS = ADMIN_STORE_STATUS_TABS;
22
24
  export function AdminStoresView({ children, ...props }) {
23
25
  const hasChildren = React.Children.count(children) > 0;
26
+ const [view, setView] = useState("table");
24
27
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
25
28
  const { openEditPanel, closePanel, isEditOpen, editId } = usePanelUrlSync();
26
29
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
@@ -78,13 +81,17 @@ export function AdminStoresView({ children, ...props }) {
78
81
  const currentPage = table.getNumber("page", 1);
79
82
  const totalPages = Math.ceil(total / PAGE_SIZE);
80
83
  const panelRow = editId ? (rows.find((r) => r.id === editId) ?? null) : null;
84
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
81
85
  if (hasChildren) {
82
86
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
83
87
  }
84
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search stores, slugs, or owner names", 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 stores found", renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [{
88
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search stores, slugs, or owner names", 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(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
89
+ { id: "manage", label: "Manage Store", variant: "primary", onClick: () => { const id = selection.selectedIds[0]; if (id)
90
+ openEditPanel(id); selection.clearSelection(); } },
91
+ ] }), 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 stores found", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next ? selection.setSelectedIds(rows.map(r => r.id)) : selection.clearSelection(), renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [{
85
92
  label: "Manage",
86
93
  onClick: () => openEditPanel(row.id),
87
- }] })) })] }), 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})` : ""] }) })] })] }))] }), _jsx(AdminStoreEditorView, { open: isEditOpen, onClose: closePanel, storeId: editId ?? undefined, storeName: panelRow?.primary, currentStatus: panelRow?.status?.toLowerCase(), currentIsVerified: Boolean(panelRow?._raw?.isVerified), currentCapabilities: Array.isArray(panelRow?._raw?.capabilities)
94
+ }] })) })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No stores found", onRowClick: (row) => openEditPanel(row.id), 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})` : ""] }) })] })] }))] }), _jsx(AdminStoreEditorView, { open: isEditOpen, onClose: closePanel, storeId: editId ?? undefined, storeName: panelRow?.primary, currentStatus: panelRow?.status?.toLowerCase(), currentIsVerified: Boolean(panelRow?._raw?.isVerified), currentCapabilities: Array.isArray(panelRow?._raw?.capabilities)
88
95
  ? panelRow._raw.capabilities
89
96
  : undefined })] }));
90
97
  }
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useCallback } from "react";
4
4
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
5
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
5
6
  import { ListingToolbar, Pagination } from "../../../ui";
6
7
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
7
8
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
@@ -15,6 +16,7 @@ const SORT_OPTIONS = [
15
16
  { value: "-createdAt", label: "Newest" },
16
17
  ];
17
18
  export function AdminSublistingCategoriesView() {
19
+ const [view, setView] = useState("table");
18
20
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
19
21
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
20
22
  const commitSearch = useCallback(() => {
@@ -49,7 +51,8 @@ export function AdminSublistingCategoriesView() {
49
51
  });
50
52
  const currentPage = table.getNumber("page", 1);
51
53
  const totalPages = Math.ceil(total / PAGE_SIZE);
54
+ const selection = useBulkSelection({ items: rows ?? [], keyExtractor: (r) => r.id });
52
55
  return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { searchValue: searchInput, searchPlaceholder: "Search sub-listing categories\u2026", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => {
53
56
  table.set("sort", v);
54
- }, 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 sub-listing categories found" })] })] }));
57
+ }, 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 })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No sub-listing categories found" })] })] }));
55
58
  }
@@ -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 } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
6
7
  import { FilterChipGroup, ListingToolbar, ListingViewShell, Pagination, RowActionMenu, } from "../../../ui";
7
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
9
  import { ADMIN_SUPPORT_TICKET_STATUS_TABS, ADMIN_SUPPORT_TICKET_PRIORITY_TABS, } from "../constants/filter-tabs";
@@ -32,6 +33,7 @@ const STATUS_BADGE = {
32
33
  };
33
34
  export function AdminSupportTicketsView({ children, ...props }) {
34
35
  const hasChildren = React.Children.count(children) > 0;
36
+ const [view, setView] = useState("table");
35
37
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
36
38
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
37
39
  const [filterOpen, setFilterOpen] = useState(false);
@@ -106,12 +108,13 @@ export function AdminSupportTicketsView({ children, ...props }) {
106
108
  });
107
109
  const currentPage = table.getNumber("page", 1);
108
110
  const totalPages = Math.ceil(total / PAGE_SIZE);
111
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
109
112
  if (hasChildren) {
110
113
  return (_jsx(ListingViewShell, { portal: "admin", ...props, children: children }));
111
114
  }
112
115
  return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by subject", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => {
113
116
  table.set("sort", v);
114
- }, 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 support tickets found", renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
117
+ }, 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 })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No support tickets found", renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
115
118
  {
116
119
  label: "View",
117
120
  onClick: () => {
@@ -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, UserPlus } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
6
7
  import { Button, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, RowActionMenu, } from "../../../ui";
7
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
9
  import { ALL_TAB } from "../constants/filter-tabs";
@@ -44,6 +45,7 @@ function formatGroup(group) {
44
45
  }
45
46
  export function AdminTeamView({ children, ...props }) {
46
47
  const hasChildren = React.Children.count(children) > 0;
48
+ const [view, setView] = useState("table");
47
49
  const table = useUrlTable({
48
50
  defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT },
49
51
  });
@@ -122,12 +124,13 @@ export function AdminTeamView({ children, ...props }) {
122
124
  });
123
125
  const currentPage = table.getNumber("page", 1);
124
126
  const totalPages = Math.ceil(total / PAGE_SIZE);
127
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
125
128
  if (hasChildren) {
126
129
  return (_jsx(ListingViewShell, { portal: "admin", ...props, children: children }));
127
130
  }
128
131
  return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by name or email", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => {
129
132
  table.set("sort", v);
130
- }, hideViewToggle: true, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { type: "button", variant: "primary", onClick: openInvite, className: "flex items-center gap-1.5 whitespace-nowrap", children: [_jsx(UserPlus, { className: "h-4 w-4" }), "Invite Employee"] }) }), 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 employees found", renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
133
+ }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { type: "button", variant: "primary", onClick: openInvite, className: "flex items-center gap-1.5 whitespace-nowrap", children: [_jsx(UserPlus, { className: "h-4 w-4" }), "Invite Employee"] }) }), 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 employees found", renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
131
134
  {
132
135
  label: "Edit Permissions",
133
136
  onClick: () => openEdit(row),
@@ -3,7 +3,9 @@ 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 { FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, RowActionMenu } from "../../../ui";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
+ import { BulkActionBar, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, RowActionMenu } from "../../../ui";
8
+ import { AdminViewCards } from "./AdminViewCards";
7
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
10
  import { ADMIN_USER_STATUS_TABS, ADMIN_USER_ROLE_TABS } from "../constants/filter-tabs";
9
11
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
@@ -21,18 +23,10 @@ const STATUS_OPTIONS = ADMIN_USER_STATUS_TABS;
21
23
  const ROLE_OPTIONS = ADMIN_USER_ROLE_TABS;
22
24
  export function AdminUsersView({ children, ...props }) {
23
25
  const hasChildren = React.Children.count(children) > 0;
26
+ const [view, setView] = useState("table");
24
27
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
25
28
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
26
29
  const [filterOpen, setFilterOpen] = useState(false);
27
- const [selectedIds, setSelectedIds] = useState(new Set());
28
- const toggleSelect = (id, next) => setSelectedIds((prev) => {
29
- const s = new Set(prev);
30
- if (next)
31
- s.add(id);
32
- else
33
- s.delete(id);
34
- return s;
35
- });
36
30
  const [pendingFilters, setPendingFilters] = useState(() => Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
37
31
  const [drawerOpen, setDrawerOpen] = useState(false);
38
32
  const [selectedRow, setSelectedRow] = useState(null);
@@ -112,13 +106,16 @@ export function AdminUsersView({ children, ...props }) {
112
106
  });
113
107
  const currentPage = table.getNumber("page", 1);
114
108
  const totalPages = Math.ceil(total / PAGE_SIZE);
109
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
115
110
  if (hasChildren) {
116
111
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
117
112
  }
118
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search users, email, or seller handles", 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 users found", selectedIds: selectedIds, onToggleSelect: toggleSelect, onToggleSelectAll: (next) => setSelectedIds(next ? new Set(rows.map((r) => r.id)) : new Set()), renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [{
113
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search users, email, or seller handles", 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(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
114
+ { id: "manage", label: "Manage Selected", variant: "primary", onClick: () => { setSelectedRow(rows.find(r => r.id === selection.selectedIds[0]) ?? null); setDrawerOpen(true); selection.clearSelection(); } },
115
+ ] }), 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 users found", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next ? selection.setSelectedIds(rows.map(r => r.id)) : selection.clearSelection(), renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [{
119
116
  label: "Manage",
120
117
  onClick: () => { setSelectedRow(row); setDrawerOpen(true); },
121
- }] })) })] }), 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" }) })] })] }), _jsxs("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(FilterChipGroup, { label: "Role", tabs: ROLE_OPTIONS, value: pendingFilters.role ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, role: 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})` : ""] }) })] })] }))] }), _jsx(AdminUserEditorView, { open: drawerOpen, onClose: () => setDrawerOpen(false), userId: selectedRow?.id, displayName: selectedRow?.primary, currentRole: toStringValue(selectedRow?._raw?.role, "user"), currentEmailVerified: Boolean(selectedRow?._raw?.emailVerified), ownedStoreId: toStringValue(selectedRow?._raw?.storeId, "") || undefined, ownedStoreName: toStringValue(selectedRow?._raw?.storeName, "") || undefined, currentIsHardBanned: Boolean((selectedRow?._raw?.isDisabled ?? selectedRow?._raw?.disabled) &&
118
+ }] })) })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No users found", onRowClick: (row) => { setSelectedRow(row); setDrawerOpen(true); }, 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" }) })] })] }), _jsxs("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(FilterChipGroup, { label: "Role", tabs: ROLE_OPTIONS, value: pendingFilters.role ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, role: 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})` : ""] }) })] })] }))] }), _jsx(AdminUserEditorView, { open: drawerOpen, onClose: () => setDrawerOpen(false), userId: selectedRow?.id, displayName: selectedRow?.primary, currentRole: toStringValue(selectedRow?._raw?.role, "user"), currentEmailVerified: Boolean(selectedRow?._raw?.emailVerified), ownedStoreId: toStringValue(selectedRow?._raw?.storeId, "") || undefined, ownedStoreName: toStringValue(selectedRow?._raw?.storeName, "") || undefined, currentIsHardBanned: Boolean((selectedRow?._raw?.isDisabled ?? selectedRow?._raw?.disabled) &&
122
119
  selectedRow?._raw?.hardBanReason), currentHardBanReason: toStringValue(selectedRow?._raw?.hardBanReason, "") || undefined, currentSoftBans: Array.isArray(selectedRow?._raw?.softBans)
123
120
  ? selectedRow._raw.softBans.map((b) => ({
124
121
  action: toStringValue(b.action, ""),
@@ -0,0 +1,12 @@
1
+ import type { AdminListingScaffoldRow } from "./AdminListingScaffold";
2
+ interface AdminViewCardsProps {
3
+ rows: AdminListingScaffoldRow[];
4
+ view: "grid" | "list";
5
+ isLoading?: boolean;
6
+ emptyLabel?: string;
7
+ onRowClick?: (row: AdminListingScaffoldRow) => void;
8
+ selectedIdSet?: Set<string>;
9
+ onToggleSelect?: (id: string) => void;
10
+ }
11
+ export declare function AdminViewCards({ rows, view, isLoading, emptyLabel, onRowClick, selectedIdSet, onToggleSelect, }: AdminViewCardsProps): import("react/jsx-runtime").JSX.Element;
12
+ export {};
@@ -0,0 +1,53 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ const FLAG_BADGES = [
4
+ { key: "featured", label: "Featured", color: "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300" },
5
+ { key: "isPromoted", label: "Promoted", color: "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300" },
6
+ { key: "isOnSale", label: "Sale", color: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300" },
7
+ { key: "isSold", label: "Sold", color: "bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400" },
8
+ ];
9
+ function StatusBadge({ status }) {
10
+ return (_jsx("span", { className: "inline-flex rounded-full bg-primary-50 px-2 py-0.5 text-xs font-medium text-primary-800 dark:bg-secondary-900/30 dark:text-secondary-300 truncate max-w-[120px]", children: status }));
11
+ }
12
+ function SkeletonCard({ view }) {
13
+ if (view === "list") {
14
+ return (_jsxs("div", { className: "flex items-center gap-3 px-4 py-3 animate-pulse", children: [_jsx("div", { className: "h-4 w-4 rounded bg-zinc-200 dark:bg-slate-700 shrink-0" }), _jsxs("div", { className: "flex-1 space-y-1.5", children: [_jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-2/3" }), _jsx("div", { className: "h-2.5 bg-zinc-200 dark:bg-slate-700 rounded w-1/3" })] }), _jsx("div", { className: "h-5 w-16 bg-zinc-200 dark:bg-slate-700 rounded-full shrink-0" }), _jsx("div", { className: "h-3 w-20 bg-zinc-200 dark:bg-slate-700 rounded shrink-0" })] }));
15
+ }
16
+ return (_jsx("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: _jsxs("div", { className: "p-4 space-y-2.5", children: [_jsx("div", { className: "h-4 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-5 bg-zinc-200 dark:bg-slate-700 rounded-full w-20 mt-1" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/3" })] }) }));
17
+ }
18
+ function AdminCardItem({ row, view, selected, onToggleSelect, onRowClick, }) {
19
+ const flags = FLAG_BADGES.filter(({ key }) => Boolean(row[key]));
20
+ const handleClick = (e) => {
21
+ if (e.target.closest('[data-checkbox]'))
22
+ return;
23
+ onRowClick?.(row);
24
+ };
25
+ if (view === "list") {
26
+ return (_jsxs("div", { className: [
27
+ "flex items-center gap-3 px-4 py-3 cursor-pointer transition-colors hover:bg-zinc-50 dark:hover:bg-slate-800/50",
28
+ selected ? "bg-primary-50/40 dark:bg-primary-900/10" : "",
29
+ ].filter(Boolean).join(" "), onClick: handleClick, role: onRowClick ? "button" : undefined, children: [onToggleSelect && (_jsx("div", { "data-checkbox": true, className: "shrink-0", onClick: (e) => { e.stopPropagation(); onToggleSelect(row.id); }, children: _jsx("input", { type: "checkbox", checked: selected, onChange: () => onToggleSelect(row.id), className: "h-4 w-4 rounded border-zinc-300 text-primary accent-primary cursor-pointer", "aria-label": `Select ${row.primary}` }) })), _jsxs("div", { className: "flex-1 min-w-0 space-y-0.5", children: [_jsx("p", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100 truncate", children: row.primary }), _jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400 truncate", children: row.secondary })] }), flags.length > 0 && (_jsx("div", { className: "hidden sm:flex items-center gap-1 shrink-0", children: flags.map(({ key, label, color }) => (_jsx("span", { className: `inline-flex rounded-full px-1.5 py-0.5 text-[10px] font-medium ${color}`, children: label }, key))) })), _jsx(StatusBadge, { status: row.status }), _jsx("span", { className: "hidden sm:block text-xs text-zinc-400 dark:text-zinc-500 shrink-0 w-24 text-right", children: row.updatedAt })] }));
30
+ }
31
+ return (_jsxs("div", { className: [
32
+ "rounded-xl border overflow-hidden cursor-pointer transition-all hover:shadow-md hover:-translate-y-0.5",
33
+ selected
34
+ ? "border-primary ring-1 ring-primary/20 bg-primary-50/30 dark:bg-primary-900/10"
35
+ : "border-zinc-100 dark:border-slate-700 bg-white dark:bg-slate-900",
36
+ ].filter(Boolean).join(" "), onClick: handleClick, role: onRowClick ? "button" : undefined, children: [onToggleSelect && (_jsxs("div", { "data-checkbox": true, className: "flex items-center gap-2 px-3 pt-3", onClick: (e) => { e.stopPropagation(); onToggleSelect(row.id); }, children: [_jsx("input", { type: "checkbox", checked: selected, onChange: () => onToggleSelect(row.id), className: "h-4 w-4 rounded border-zinc-300 text-primary accent-primary cursor-pointer", "aria-label": `Select ${row.primary}` }), flags.length > 0 && (_jsx("div", { className: "flex items-center gap-1 flex-wrap", children: flags.map(({ key, label, color }) => (_jsx("span", { className: `inline-flex rounded-full px-1.5 py-0.5 text-[10px] font-medium ${color}`, children: label }, key))) }))] })), _jsxs("div", { className: "p-3.5 space-y-2", children: [_jsxs("div", { className: "space-y-0.5", children: [_jsx("p", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100 line-clamp-2 leading-snug", children: row.primary }), _jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400 truncate", children: row.secondary })] }), _jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx(StatusBadge, { status: row.status }), _jsx("span", { className: "text-[11px] text-zinc-400 dark:text-zinc-500 shrink-0", children: row.updatedAt })] })] })] }));
37
+ }
38
+ export function AdminViewCards({ rows, view, isLoading, emptyLabel = "No items found", onRowClick, selectedIdSet, onToggleSelect, }) {
39
+ if (isLoading) {
40
+ const count = view === "grid" ? 12 : 8;
41
+ if (view === "list") {
42
+ return (_jsx("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden divide-y divide-zinc-100 dark:divide-slate-700", children: Array.from({ length: count }).map((_, i) => _jsx(SkeletonCard, { view: "list" }, i)) }));
43
+ }
44
+ return (_jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4", children: Array.from({ length: count }).map((_, i) => _jsx(SkeletonCard, { view: "grid" }, i)) }));
45
+ }
46
+ if (rows.length === 0) {
47
+ return (_jsx("p", { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: emptyLabel }));
48
+ }
49
+ if (view === "list") {
50
+ return (_jsx("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden divide-y divide-zinc-100 dark:divide-slate-700", children: rows.map((row) => (_jsx(AdminCardItem, { row: row, view: "list", selected: selectedIdSet?.has(row.id) ?? false, onToggleSelect: onToggleSelect, onRowClick: onRowClick }, row.id))) }));
51
+ }
52
+ return (_jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4", children: rows.map((row) => (_jsx(AdminCardItem, { row: row, view: "grid", selected: selectedIdSet?.has(row.id) ?? false, onToggleSelect: onToggleSelect, onRowClick: onRowClick }, row.id))) }));
53
+ }
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React, { useState, useCallback } from "react";
4
4
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
5
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
5
6
  import { ListingToolbar, Pagination, ListingViewShell } from "../../../ui";
6
7
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
7
8
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
@@ -15,6 +16,7 @@ const SORT_OPTIONS = [
15
16
  ];
16
17
  export function AdminWishlistsView({ children, ...props }) {
17
18
  const hasChildren = React.Children.count(children) > 0;
19
+ const [view, setView] = useState("table");
18
20
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
19
21
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
20
22
  const [selectedIds, setSelectedIds] = useState(new Set());
@@ -57,8 +59,9 @@ export function AdminWishlistsView({ children, ...props }) {
57
59
  });
58
60
  const currentPage = table.getNumber("page", 1);
59
61
  const totalPages = Math.ceil(total / PAGE_SIZE);
62
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
60
63
  if (hasChildren) {
61
64
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
62
65
  }
63
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search by user ID", 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 user wishlists found", selectedIds: selectedIds, onToggleSelect: toggleSelect, onToggleSelectAll: (next) => setSelectedIds(next ? new Set(rows.map((r) => r.id)) : new Set()) })] })] }));
66
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search by user ID", 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 })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No user wishlists found", selectedIds: selectedIds, onToggleSelect: toggleSelect, onToggleSelectAll: (next) => setSelectedIds(next ? new Set(rows.map((r) => r.id)) : new Set()) })] })] }));
64
67
  }
@@ -7,12 +7,7 @@ import { BaseRepository, getFirestoreCount, prepareForFirestore, } from "../../.
7
7
  import { DatabaseError } from "../../../errors";
8
8
  import { BID_COLLECTION, createBidId } from "../schemas";
9
9
  import { encryptPiiFields, decryptPiiFields, BID_PII_FIELDS, } from "../../../security";
10
- const BID_FIELDS = {
11
- PRODUCT_ID: "productId",
12
- USER_ID: "userId",
13
- STATUS: "status",
14
- IS_WINNING: "isWinning",
15
- };
10
+ import { BID_FIELDS } from "../../../constants/field-names";
16
11
  export class BidRepository extends BaseRepository {
17
12
  constructor() {
18
13
  super(BID_COLLECTION);
@@ -65,8 +65,8 @@ export declare const auctionItemSchema: z.ZodObject<{
65
65
  status: "draft" | "published" | "archived" | "sold";
66
66
  title: string;
67
67
  price: number;
68
- listingType: "auction";
69
68
  featured: boolean;
69
+ listingType: "auction";
70
70
  slug: string;
71
71
  auctionEndDate: string;
72
72
  startingBid: number;
@@ -94,8 +94,8 @@ export declare const auctionItemSchema: z.ZodObject<{
94
94
  status: "draft" | "published" | "archived" | "sold";
95
95
  title: string;
96
96
  price: number;
97
- listingType: "auction";
98
97
  featured: boolean;
98
+ listingType: "auction";
99
99
  slug: string;
100
100
  auctionEndDate: string;
101
101
  startingBid: number;
@@ -129,18 +129,18 @@ export declare const bidRecordSchema: z.ZodObject<{
129
129
  currency: string;
130
130
  id: string;
131
131
  amount: number;
132
- isWinning: boolean;
133
132
  auctionId: string;
134
133
  bidderId: string;
134
+ isWinning: boolean;
135
135
  placedAt: string;
136
136
  bidderDisplayName?: string | undefined;
137
137
  }, {
138
138
  currency: string;
139
139
  id: string;
140
140
  amount: number;
141
- isWinning: boolean;
142
141
  auctionId: string;
143
142
  bidderId: string;
143
+ isWinning: boolean;
144
144
  placedAt: string;
145
145
  bidderDisplayName?: string | undefined;
146
146
  }>;
@@ -73,10 +73,11 @@ export declare const createBlogPostSchema: z.ZodObject<{
73
73
  title: string;
74
74
  content: string;
75
75
  category: "news" | "tips" | "guides" | "updates" | "community";
76
- slug: string;
77
76
  tags: string[];
78
77
  isFeatured: boolean;
78
+ slug: string;
79
79
  excerpt: string;
80
+ readTimeMinutes: number;
80
81
  contentImages: {
81
82
  type: "video" | "image" | "file";
82
83
  url: string;
@@ -89,7 +90,6 @@ export declare const createBlogPostSchema: z.ZodObject<{
89
90
  alt?: string | undefined;
90
91
  thumbnailUrl?: string | undefined;
91
92
  }[];
92
- readTimeMinutes: number;
93
93
  publishedAt?: string | undefined;
94
94
  coverImage?: {
95
95
  type: "video" | "image" | "file";
@@ -113,14 +113,17 @@ export declare const createBlogPostSchema: z.ZodObject<{
113
113
  excerpt: string;
114
114
  status?: "draft" | "published" | "archived" | undefined;
115
115
  tags?: string[] | undefined;
116
- publishedAt?: string | undefined;
117
116
  isFeatured?: boolean | undefined;
117
+ publishedAt?: string | undefined;
118
118
  coverImage?: string | {
119
119
  type: "video" | "image" | "file";
120
120
  url: string;
121
121
  alt?: string | undefined;
122
122
  thumbnailUrl?: string | undefined;
123
123
  } | null | undefined;
124
+ authorId?: string | undefined;
125
+ authorName?: string | undefined;
126
+ readTimeMinutes?: number | undefined;
124
127
  contentImages?: {
125
128
  type: "video" | "image" | "file";
126
129
  url: string;
@@ -133,10 +136,7 @@ export declare const createBlogPostSchema: z.ZodObject<{
133
136
  alt?: string | undefined;
134
137
  thumbnailUrl?: string | undefined;
135
138
  }[] | undefined;
136
- authorId?: string | undefined;
137
- authorName?: string | undefined;
138
139
  authorAvatar?: string | undefined;
139
- readTimeMinutes?: number | undefined;
140
140
  metaTitle?: string | undefined;
141
141
  metaDescription?: string | undefined;
142
142
  }>;
@@ -212,10 +212,10 @@ export declare const updateBlogPostSchema: z.ZodObject<{
212
212
  title?: string | undefined;
213
213
  content?: string | undefined;
214
214
  category?: "news" | "tips" | "guides" | "updates" | "community" | undefined;
215
- slug?: string | undefined;
216
215
  tags?: string[] | undefined;
217
- publishedAt?: string | undefined;
218
216
  isFeatured?: boolean | undefined;
217
+ slug?: string | undefined;
218
+ publishedAt?: string | undefined;
219
219
  coverImage?: {
220
220
  type: "video" | "image" | "file";
221
221
  url: string;
@@ -226,6 +226,9 @@ export declare const updateBlogPostSchema: z.ZodObject<{
226
226
  type: "image";
227
227
  } | null | undefined;
228
228
  excerpt?: string | undefined;
229
+ authorId?: string | undefined;
230
+ authorName?: string | undefined;
231
+ readTimeMinutes?: number | undefined;
229
232
  contentImages?: {
230
233
  type: "video" | "image" | "file";
231
234
  url: string;
@@ -238,10 +241,7 @@ export declare const updateBlogPostSchema: z.ZodObject<{
238
241
  alt?: string | undefined;
239
242
  thumbnailUrl?: string | undefined;
240
243
  }[] | undefined;
241
- authorId?: string | undefined;
242
- authorName?: string | undefined;
243
244
  authorAvatar?: string | undefined;
244
- readTimeMinutes?: number | undefined;
245
245
  metaTitle?: string | undefined;
246
246
  metaDescription?: string | undefined;
247
247
  }, {
@@ -249,10 +249,10 @@ export declare const updateBlogPostSchema: z.ZodObject<{
249
249
  title?: string | undefined;
250
250
  content?: string | undefined;
251
251
  category?: "news" | "tips" | "guides" | "updates" | "community" | undefined;
252
- slug?: string | undefined;
253
252
  tags?: string[] | undefined;
254
- publishedAt?: string | undefined;
255
253
  isFeatured?: boolean | undefined;
254
+ slug?: string | undefined;
255
+ publishedAt?: string | undefined;
256
256
  coverImage?: string | {
257
257
  type: "video" | "image" | "file";
258
258
  url: string;
@@ -260,6 +260,9 @@ export declare const updateBlogPostSchema: z.ZodObject<{
260
260
  thumbnailUrl?: string | undefined;
261
261
  } | null | undefined;
262
262
  excerpt?: string | undefined;
263
+ authorId?: string | undefined;
264
+ authorName?: string | undefined;
265
+ readTimeMinutes?: number | undefined;
263
266
  contentImages?: {
264
267
  type: "video" | "image" | "file";
265
268
  url: string;
@@ -272,10 +275,7 @@ export declare const updateBlogPostSchema: z.ZodObject<{
272
275
  alt?: string | undefined;
273
276
  thumbnailUrl?: string | undefined;
274
277
  }[] | undefined;
275
- authorId?: string | undefined;
276
- authorName?: string | undefined;
277
278
  authorAvatar?: string | undefined;
278
- readTimeMinutes?: number | undefined;
279
279
  metaTitle?: string | undefined;
280
280
  metaDescription?: string | undefined;
281
281
  }>;
@@ -6,91 +6,91 @@ export declare const BLOG_FILTER_KEYS: {
6
6
  readonly public: readonly ["category"];
7
7
  };
8
8
  export declare const BLOG_ADMIN_SORT_OPTIONS: readonly [{
9
- readonly value: "-createdAt";
9
+ readonly value: string;
10
10
  readonly label: "Newest First";
11
11
  }, {
12
- readonly value: "createdAt";
12
+ readonly value: string;
13
13
  readonly label: "Oldest First";
14
14
  }, {
15
- readonly value: "title";
15
+ readonly value: string;
16
16
  readonly label: "Title A–Z";
17
17
  }, {
18
- readonly value: "-title";
18
+ readonly value: string;
19
19
  readonly label: "Title Z–A";
20
20
  }, {
21
- readonly value: "-views";
21
+ readonly value: string;
22
22
  readonly label: "Most Viewed";
23
23
  }, {
24
- readonly value: "-readTimeMinutes";
24
+ readonly value: string;
25
25
  readonly label: "Longest Read";
26
26
  }, {
27
- readonly value: "-publishedAt";
27
+ readonly value: string;
28
28
  readonly label: "Published: Newest";
29
29
  }, {
30
- readonly value: "publishedAt";
30
+ readonly value: string;
31
31
  readonly label: "Published: Oldest";
32
32
  }];
33
33
  export declare const BLOG_SELLER_SORT_OPTIONS: readonly [{
34
- readonly value: "-createdAt";
34
+ readonly value: string;
35
35
  readonly label: "Newest First";
36
36
  }, {
37
- readonly value: "createdAt";
37
+ readonly value: string;
38
38
  readonly label: "Oldest First";
39
39
  }, {
40
- readonly value: "title";
40
+ readonly value: string;
41
41
  readonly label: "Title A–Z";
42
42
  }, {
43
- readonly value: "-title";
43
+ readonly value: string;
44
44
  readonly label: "Title Z–A";
45
45
  }, {
46
- readonly value: "-views";
46
+ readonly value: string;
47
47
  readonly label: "Most Viewed";
48
48
  }, {
49
- readonly value: "-readTimeMinutes";
49
+ readonly value: string;
50
50
  readonly label: "Longest Read";
51
51
  }, {
52
- readonly value: "-publishedAt";
52
+ readonly value: string;
53
53
  readonly label: "Published: Newest";
54
54
  }, {
55
- readonly value: "publishedAt";
55
+ readonly value: string;
56
56
  readonly label: "Published: Oldest";
57
57
  }];
58
58
  export declare const BLOG_PUBLIC_SORT_OPTIONS: readonly [{
59
- readonly value: "-publishedAt";
59
+ readonly value: string;
60
60
  readonly label: "Published: Newest";
61
61
  }, {
62
- readonly value: "publishedAt";
62
+ readonly value: string;
63
63
  readonly label: "Published: Oldest";
64
64
  }, {
65
- readonly value: "-views";
65
+ readonly value: string;
66
66
  readonly label: "Most Viewed";
67
67
  }, {
68
- readonly value: "title";
68
+ readonly value: string;
69
69
  readonly label: "Title A–Z";
70
70
  }];
71
71
  export declare const BLOG_SORT_OPTIONS: readonly [{
72
- readonly value: "-createdAt";
72
+ readonly value: string;
73
73
  readonly label: "Newest First";
74
74
  }, {
75
- readonly value: "createdAt";
75
+ readonly value: string;
76
76
  readonly label: "Oldest First";
77
77
  }, {
78
- readonly value: "title";
78
+ readonly value: string;
79
79
  readonly label: "Title A–Z";
80
80
  }, {
81
- readonly value: "-title";
81
+ readonly value: string;
82
82
  readonly label: "Title Z–A";
83
83
  }, {
84
- readonly value: "-views";
84
+ readonly value: string;
85
85
  readonly label: "Most Viewed";
86
86
  }, {
87
- readonly value: "-readTimeMinutes";
87
+ readonly value: string;
88
88
  readonly label: "Longest Read";
89
89
  }, {
90
- readonly value: "-publishedAt";
90
+ readonly value: string;
91
91
  readonly label: "Published: Newest";
92
92
  }, {
93
- readonly value: "publishedAt";
93
+ readonly value: string;
94
94
  readonly label: "Published: Oldest";
95
95
  }];
96
96
  export declare function getBlogFilterKeys(variant: BlogFilterVariant): readonly string[];