@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.
- package/dist/_internal/server/jobs/core/assignSpinPrize.js +8 -7
- package/dist/_internal/server/jobs/core/bundleStockSync.js +11 -6
- package/dist/_internal/server/jobs/core/countersReconcile.js +3 -2
- package/dist/_internal/server/jobs/core/onProductStockChange.js +12 -7
- package/dist/_internal/server/jobs/core/onProductWrite.js +3 -2
- package/dist/_internal/server/jobs/core/prizeRevealClose.js +7 -5
- package/dist/_internal/server/jobs/core/prizeRevealExpiry.js +11 -6
- package/dist/_internal/server/jobs/core/prizeRevealOpen.js +13 -7
- package/dist/_internal/server/jobs/core/prizeRevealReminder.js +9 -4
- package/dist/_internal/server/jobs/core/triggerEventRaffle.js +3 -2
- package/dist/_internal/shared/features/blog/schema.d.ts +4 -4
- package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +32 -32
- package/dist/_internal/shared/features/events/schema.d.ts +4 -4
- package/dist/_internal/shared/features/products/schema.d.ts +20 -20
- package/dist/_internal/shared/features/promotions/schema.d.ts +2 -2
- package/dist/_internal/shared/features/reviews/schema.d.ts +1 -1
- package/dist/constants/field-names.d.ts +813 -0
- package/dist/constants/field-names.js +918 -0
- package/dist/constants/sort.d.ts +7 -0
- package/dist/constants/sort.js +5 -0
- package/dist/constants/table-keys.d.ts +48 -0
- package/dist/constants/table-keys.js +46 -0
- package/dist/features/account/components/AddressesIndexListing.js +9 -6
- package/dist/features/admin/components/AdminAllEventEntriesView.js +4 -1
- package/dist/features/admin/components/AdminBidsView.js +4 -1
- package/dist/features/admin/components/AdminBlogView.js +9 -2
- package/dist/features/admin/components/AdminBrandsView.js +9 -2
- package/dist/features/admin/components/AdminCarouselView.js +4 -1
- package/dist/features/admin/components/AdminCartsView.js +4 -1
- package/dist/features/admin/components/AdminCategoriesView.js +9 -2
- package/dist/features/admin/components/AdminContactView.js +4 -1
- package/dist/features/admin/components/AdminCouponsView.js +4 -1
- package/dist/features/admin/components/AdminFaqsView.js +5 -1
- package/dist/features/admin/components/AdminFeaturesView.js +5 -2
- package/dist/features/admin/components/AdminHistoryView.js +5 -1
- package/dist/features/admin/components/AdminNewsletterView.js +4 -1
- package/dist/features/admin/components/AdminNotificationsView.js +4 -1
- package/dist/features/admin/components/AdminOrdersView.js +14 -12
- package/dist/features/admin/components/AdminPayoutsView.js +4 -1
- package/dist/features/admin/components/AdminPrizeDrawsView.js +4 -1
- package/dist/features/admin/components/AdminProductsView.js +14 -3
- package/dist/features/admin/components/AdminReturnRequestsView.js +4 -1
- package/dist/features/admin/components/AdminReviewsView.js +4 -1
- package/dist/features/admin/components/AdminScammersView.js +4 -1
- package/dist/features/admin/components/AdminSessionsView.js +4 -1
- package/dist/features/admin/components/AdminStoreAddressesView.js +5 -1
- package/dist/features/admin/components/AdminStoresView.js +10 -3
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +4 -1
- package/dist/features/admin/components/AdminSupportTicketsView.js +4 -1
- package/dist/features/admin/components/AdminTeamView.js +4 -1
- package/dist/features/admin/components/AdminUsersView.js +9 -12
- package/dist/features/admin/components/AdminViewCards.d.ts +12 -0
- package/dist/features/admin/components/AdminViewCards.js +53 -0
- package/dist/features/admin/components/AdminWishlistsView.js +4 -1
- package/dist/features/auctions/repository/bid.repository.js +1 -6
- package/dist/features/auctions/schemas/index.d.ts +4 -4
- package/dist/features/blog/actions/blog-actions.d.ts +16 -16
- package/dist/features/blog/components/BlogFilters.d.ts +28 -28
- package/dist/features/blog/components/BlogFilters.js +18 -15
- package/dist/features/blog/components/BlogIndexListing.js +20 -16
- package/dist/features/blog/schemas/index.d.ts +10 -10
- package/dist/features/categories/components/CategoriesIndexListing.js +25 -21
- package/dist/features/categories/schemas/index.d.ts +2 -2
- package/dist/features/events/components/AdminEventsView.js +5 -1
- package/dist/features/events/components/EventFilters.d.ts +24 -24
- package/dist/features/events/components/EventFilters.js +16 -13
- package/dist/features/events/components/EventStatusBadge.js +1 -0
- package/dist/features/events/components/EventsIndexListing.js +31 -26
- package/dist/features/events/components/EventsListPageView.js +13 -10
- package/dist/features/events/schemas/firestore.d.ts +1 -0
- package/dist/features/events/schemas/firestore.js +1 -0
- package/dist/features/events/schemas/index.d.ts +1 -1
- package/dist/features/events/types/index.d.ts +1 -1
- package/dist/features/faq/actions/faq-actions.d.ts +10 -10
- package/dist/features/faq/schemas/index.d.ts +4 -4
- package/dist/features/orders/components/OrderFilters.d.ts +20 -20
- package/dist/features/orders/components/OrderFilters.js +33 -30
- package/dist/features/orders/repository/orders.repository.js +1 -15
- package/dist/features/orders/schemas/index.d.ts +2 -2
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +35 -31
- package/dist/features/pre-orders/schemas/index.d.ts +3 -3
- package/dist/features/products/components/AuctionsIndexListing.js +34 -30
- package/dist/features/products/components/BidHistory.d.ts +0 -1
- package/dist/features/products/components/BidHistory.js +1 -1
- package/dist/features/products/components/PrizeDrawsIndexListing.js +44 -40
- package/dist/features/products/components/ProductDetailPageView.js +1 -1
- package/dist/features/products/components/ProductFilters.d.ts +20 -20
- package/dist/features/products/components/ProductFilters.js +62 -59
- package/dist/features/products/components/ProductsIndexListing.js +28 -24
- package/dist/features/products/components/ProductsIndexPageView.js +18 -10
- package/dist/features/products/hooks/useProducts.js +2 -0
- package/dist/features/products/repository/products.repository.js +2 -22
- package/dist/features/products/schemas/index.d.ts +28 -28
- package/dist/features/products/schemas/product-features.validators.d.ts +12 -12
- package/dist/features/products/types/index.d.ts +2 -0
- package/dist/features/promotions/components/CouponsIndexListing.js +21 -17
- package/dist/features/promotions/schemas/index.d.ts +4 -4
- package/dist/features/reviews/components/ReviewDetailShell.js +6 -5
- package/dist/features/reviews/components/ReviewFilters.d.ts +16 -16
- package/dist/features/reviews/components/ReviewFilters.js +10 -7
- package/dist/features/reviews/components/ReviewsIndexListing.js +21 -17
- package/dist/features/reviews/components/ReviewsList.d.ts +5 -2
- package/dist/features/reviews/components/ReviewsList.js +11 -5
- package/dist/features/reviews/schemas/firestore.d.ts +1 -0
- package/dist/features/reviews/schemas/index.d.ts +8 -8
- package/dist/features/reviews/types/index.d.ts +1 -0
- package/dist/features/search/schemas/index.d.ts +2 -2
- package/dist/features/seller/components/SellerAuctionsView.js +5 -1
- package/dist/features/seller/components/SellerBidsView.js +4 -1
- package/dist/features/seller/components/SellerCouponsView.js +4 -1
- package/dist/features/seller/components/SellerOffersView.js +5 -1
- package/dist/features/seller/components/SellerOrdersView.js +4 -1
- package/dist/features/seller/components/SellerPayoutsView.js +5 -1
- package/dist/features/seller/components/SellerPreOrdersView.js +4 -1
- package/dist/features/seller/components/SellerPrizeDrawsView.js +4 -1
- package/dist/features/seller/components/SellerProductsView.js +4 -1
- package/dist/features/seller/schemas/index.d.ts +10 -10
- package/dist/features/stores/api/route.js +10 -1
- package/dist/features/stores/components/StoreReviewsListing.js +1 -1
- package/dist/features/stores/components/StoresIndexListing.js +24 -20
- package/dist/features/stores/schemas/firestore.d.ts +1 -0
- package/dist/features/stores/schemas/index.d.ts +4 -4
- package/dist/index.d.ts +8 -1
- package/dist/index.js +12 -1
- package/dist/react/hooks/useUrlTable.js +2 -1
- package/dist/seed/bids-seed-data.js +45 -44
- package/dist/seed/categories-seed-data.js +41 -40
- package/dist/seed/coupons-seed-data.js +41 -40
- package/dist/seed/products-auctions-seed-data.js +54 -53
- package/dist/seed/products-preorders-seed-data.js +25 -24
- package/dist/seed/products-prize-draws-seed-data.js +24 -23
- package/dist/seed/products-standard-seed-data.js +294 -293
- package/dist/seed/reviews-seed-data.js +61 -60
- package/dist/seed/scammers-seed-data.js +3 -2
- package/dist/seed/stores-seed-data.js +9 -8
- package/dist/seed/support-tickets-seed-data.js +13 -12
- package/dist/seed/users-seed-data.js +28 -27
- package/dist/tailwind-utilities.css +1 -1
- package/dist/utils/sieve-builder.d.ts +41 -0
- package/dist/utils/sieve-builder.js +66 -0
- package/package.json +1 -1
|
@@ -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 {
|
|
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); },
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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 {
|
|
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); },
|
|
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); },
|
|
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
|
-
|
|
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:
|
|
9
|
+
readonly value: string;
|
|
10
10
|
readonly label: "Newest First";
|
|
11
11
|
}, {
|
|
12
|
-
readonly value:
|
|
12
|
+
readonly value: string;
|
|
13
13
|
readonly label: "Oldest First";
|
|
14
14
|
}, {
|
|
15
|
-
readonly value:
|
|
15
|
+
readonly value: string;
|
|
16
16
|
readonly label: "Title A–Z";
|
|
17
17
|
}, {
|
|
18
|
-
readonly value:
|
|
18
|
+
readonly value: string;
|
|
19
19
|
readonly label: "Title Z–A";
|
|
20
20
|
}, {
|
|
21
|
-
readonly value:
|
|
21
|
+
readonly value: string;
|
|
22
22
|
readonly label: "Most Viewed";
|
|
23
23
|
}, {
|
|
24
|
-
readonly value:
|
|
24
|
+
readonly value: string;
|
|
25
25
|
readonly label: "Longest Read";
|
|
26
26
|
}, {
|
|
27
|
-
readonly value:
|
|
27
|
+
readonly value: string;
|
|
28
28
|
readonly label: "Published: Newest";
|
|
29
29
|
}, {
|
|
30
|
-
readonly value:
|
|
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:
|
|
34
|
+
readonly value: string;
|
|
35
35
|
readonly label: "Newest First";
|
|
36
36
|
}, {
|
|
37
|
-
readonly value:
|
|
37
|
+
readonly value: string;
|
|
38
38
|
readonly label: "Oldest First";
|
|
39
39
|
}, {
|
|
40
|
-
readonly value:
|
|
40
|
+
readonly value: string;
|
|
41
41
|
readonly label: "Title A–Z";
|
|
42
42
|
}, {
|
|
43
|
-
readonly value:
|
|
43
|
+
readonly value: string;
|
|
44
44
|
readonly label: "Title Z–A";
|
|
45
45
|
}, {
|
|
46
|
-
readonly value:
|
|
46
|
+
readonly value: string;
|
|
47
47
|
readonly label: "Most Viewed";
|
|
48
48
|
}, {
|
|
49
|
-
readonly value:
|
|
49
|
+
readonly value: string;
|
|
50
50
|
readonly label: "Longest Read";
|
|
51
51
|
}, {
|
|
52
|
-
readonly value:
|
|
52
|
+
readonly value: string;
|
|
53
53
|
readonly label: "Published: Newest";
|
|
54
54
|
}, {
|
|
55
|
-
readonly value:
|
|
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:
|
|
59
|
+
readonly value: string;
|
|
60
60
|
readonly label: "Published: Newest";
|
|
61
61
|
}, {
|
|
62
|
-
readonly value:
|
|
62
|
+
readonly value: string;
|
|
63
63
|
readonly label: "Published: Oldest";
|
|
64
64
|
}, {
|
|
65
|
-
readonly value:
|
|
65
|
+
readonly value: string;
|
|
66
66
|
readonly label: "Most Viewed";
|
|
67
67
|
}, {
|
|
68
|
-
readonly value:
|
|
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:
|
|
72
|
+
readonly value: string;
|
|
73
73
|
readonly label: "Newest First";
|
|
74
74
|
}, {
|
|
75
|
-
readonly value:
|
|
75
|
+
readonly value: string;
|
|
76
76
|
readonly label: "Oldest First";
|
|
77
77
|
}, {
|
|
78
|
-
readonly value:
|
|
78
|
+
readonly value: string;
|
|
79
79
|
readonly label: "Title A–Z";
|
|
80
80
|
}, {
|
|
81
|
-
readonly value:
|
|
81
|
+
readonly value: string;
|
|
82
82
|
readonly label: "Title Z–A";
|
|
83
83
|
}, {
|
|
84
|
-
readonly value:
|
|
84
|
+
readonly value: string;
|
|
85
85
|
readonly label: "Most Viewed";
|
|
86
86
|
}, {
|
|
87
|
-
readonly value:
|
|
87
|
+
readonly value: string;
|
|
88
88
|
readonly label: "Longest Read";
|
|
89
89
|
}, {
|
|
90
|
-
readonly value:
|
|
90
|
+
readonly value: string;
|
|
91
91
|
readonly label: "Published: Newest";
|
|
92
92
|
}, {
|
|
93
|
-
readonly value:
|
|
93
|
+
readonly value: string;
|
|
94
94
|
readonly label: "Published: Oldest";
|
|
95
95
|
}];
|
|
96
96
|
export declare function getBlogFilterKeys(variant: BlogFilterVariant): readonly string[];
|