@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
|
@@ -27,8 +27,8 @@ export declare const searchProductItemSchema: z.ZodObject<{
|
|
|
27
27
|
currency?: string | undefined;
|
|
28
28
|
status?: string | undefined;
|
|
29
29
|
price?: number | undefined;
|
|
30
|
-
listingType?: "auction" | "standard" | "pre-order" | "prize-draw" | "bundle" | undefined;
|
|
31
30
|
featured?: boolean | undefined;
|
|
31
|
+
listingType?: "auction" | "standard" | "pre-order" | "prize-draw" | "bundle" | undefined;
|
|
32
32
|
isPromoted?: boolean | undefined;
|
|
33
33
|
mainImage?: string | undefined;
|
|
34
34
|
}, {
|
|
@@ -38,8 +38,8 @@ export declare const searchProductItemSchema: z.ZodObject<{
|
|
|
38
38
|
currency?: string | undefined;
|
|
39
39
|
status?: string | undefined;
|
|
40
40
|
price?: number | undefined;
|
|
41
|
-
listingType?: "auction" | "standard" | "pre-order" | "prize-draw" | "bundle" | undefined;
|
|
42
41
|
featured?: boolean | undefined;
|
|
42
|
+
listingType?: "auction" | "standard" | "pre-order" | "prize-draw" | "bundle" | undefined;
|
|
43
43
|
isPromoted?: boolean | undefined;
|
|
44
44
|
mainImage?: string | undefined;
|
|
45
45
|
}>;
|
|
@@ -3,6 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import React, { useState, useCallback } from "react";
|
|
4
4
|
import { X } from "lucide-react";
|
|
5
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
|
+
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
7
|
+
import { AdminViewCards } from "../../admin/components/AdminViewCards";
|
|
6
8
|
import { FilterChipGroup, ListingToolbar, Pagination, ListingViewShell } from "../../../ui";
|
|
7
9
|
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
10
|
import { SELLER_AUCTION_STATUS_TABS } from "../../admin/constants/filter-tabs";
|
|
@@ -20,6 +22,7 @@ const SORT_OPTIONS = [
|
|
|
20
22
|
const STATUS_OPTIONS = SELLER_AUCTION_STATUS_TABS;
|
|
21
23
|
export function SellerAuctionsView({ renderHeader, children, ...props }) {
|
|
22
24
|
const hasChildren = React.Children.count(children) > 0;
|
|
25
|
+
const [view, setView] = useState("table");
|
|
23
26
|
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
24
27
|
const [searchInput, setSearchInput] = useState(table.get("q") || "");
|
|
25
28
|
const [filterOpen, setFilterOpen] = useState(false);
|
|
@@ -74,8 +77,9 @@ export function SellerAuctionsView({ renderHeader, children, ...props }) {
|
|
|
74
77
|
});
|
|
75
78
|
const currentPage = table.getNumber("page", 1);
|
|
76
79
|
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
80
|
+
const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
|
|
77
81
|
if (hasChildren) {
|
|
78
82
|
return _jsx(ListingViewShell, { portal: "seller", ...props, children: children });
|
|
79
83
|
}
|
|
80
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search auctions by product name", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); },
|
|
84
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search auctions by product name", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No auctions found" })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No auctions found", onRowClick: undefined, selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }));
|
|
81
85
|
}
|
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import { useState, useCallback } from "react";
|
|
4
4
|
import { X } from "lucide-react";
|
|
5
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
|
+
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
6
7
|
import { Badge, Div, FilterChipGroup, ListingToolbar, Pagination } from "../../../ui";
|
|
7
8
|
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
9
|
import { SELLER_BID_STATUS_TABS } from "../../admin/constants/filter-tabs";
|
|
@@ -34,6 +35,7 @@ const STATUS_BADGE = {
|
|
|
34
35
|
// ---------------------------------------------------------------------------
|
|
35
36
|
export function SellerBidsView({ endpoint = SELLER_ENDPOINTS.BIDS }) {
|
|
36
37
|
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
38
|
+
const [view, setView] = useState("table");
|
|
37
39
|
const [searchInput, setSearchInput] = useState(table.get("q") || "");
|
|
38
40
|
const [filterOpen, setFilterOpen] = useState(false);
|
|
39
41
|
const [pendingFilters, setPendingFilters] = useState(() => Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
|
|
@@ -119,5 +121,6 @@ export function SellerBidsView({ endpoint = SELLER_ENDPOINTS.BIDS }) {
|
|
|
119
121
|
render: (row) => _jsx("span", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: row.bidDate }),
|
|
120
122
|
},
|
|
121
123
|
];
|
|
122
|
-
|
|
124
|
+
const selection = useBulkSelection({ items: rows ?? [], keyExtractor: (r) => r.id });
|
|
125
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by bidder name", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Div, { className: "mb-4 rounded-xl border border-red-200 bg-red-50 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, columns: columns, isLoading: isLoading, emptyLabel: "No bids found for your auctions" })] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsx(FilterChipGroup, { label: "Bid Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })), allId: "" }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-[var(--appkit-color-primary)] py-2.5 text-sm font-semibold text-white transition-colors active:scale-[0.98]", children: ["Apply", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }));
|
|
123
126
|
}
|
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import React, { useState, useCallback } from "react";
|
|
4
4
|
import { X, Plus } from "lucide-react";
|
|
5
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
|
+
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
6
7
|
import { Button, Div, ListingToolbar, Pagination, ListingViewShell, Text } from "../../../ui";
|
|
7
8
|
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
9
|
import { toRecordArray, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
|
|
@@ -30,6 +31,7 @@ function getIsActive(item) {
|
|
|
30
31
|
// ---------------------------------------------------------------------------
|
|
31
32
|
export function SellerCouponsView({ onCreateClick, onEditClick, onToggle, onDelete, children, ...props }) {
|
|
32
33
|
const hasChildren = React.Children.count(children) > 0;
|
|
34
|
+
const [view, setView] = useState("table");
|
|
33
35
|
const [togglingId, setTogglingId] = useState(null);
|
|
34
36
|
const [deletingId, setDeletingId] = useState(null);
|
|
35
37
|
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
@@ -104,10 +106,11 @@ export function SellerCouponsView({ onCreateClick, onEditClick, onToggle, onDele
|
|
|
104
106
|
setDeletingId(null);
|
|
105
107
|
}
|
|
106
108
|
}, [onDelete, refetch]);
|
|
109
|
+
const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
|
|
107
110
|
if (hasChildren) {
|
|
108
111
|
return _jsx(ListingViewShell, { portal: "seller", ...props, children: children });
|
|
109
112
|
}
|
|
110
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by coupon code", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); },
|
|
113
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by coupon code", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: onCreateClick
|
|
111
114
|
? (_jsxs(Button, { size: "sm", onClick: onCreateClick, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: "Add Coupon" })] }))
|
|
112
115
|
: undefined }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Div, { className: "mb-4 rounded-xl border border-red-200 bg-red-50 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), isLoading ? (_jsx(Div, { className: "fluid-grid-card gap-3", children: Array.from({ length: 6 }).map((_, i) => (_jsxs(Div, { className: "rounded-xl border-2 border-zinc-100 dark:border-slate-700 p-4 animate-pulse space-y-3", children: [_jsx(Div, { className: "h-6 bg-zinc-200 dark:bg-slate-700 rounded w-2/3" }), _jsx(Div, { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-full" }), _jsx(Div, { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" })] }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "py-16 text-center", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: "No coupons found \u2014 create your first coupon" }) })) : (_jsx(Div, { className: "fluid-grid-card gap-3", children: rows.map((row) => (_jsx(CouponCard, { coupon: row.raw, onEdit: onEditClick ? handleEdit : undefined, onToggleActive: onToggle ? handleToggle : undefined, onDelete: onDelete ? handleDelete : undefined, className: togglingId === row.id || deletingId === row.id ? "pointer-events-none opacity-60" : undefined }, row.id))) }))] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400", children: "Status" }), _jsx("div", { className: "flex flex-wrap gap-2", children: [{ label: "All", value: "" }, { label: "Active", value: "true" }, { label: "Inactive", value: "false" }].map((opt) => (_jsx("button", { type: "button", onClick: () => setPendingFilters((p) => ({ ...p, isActive: opt.value })), className: `rounded-full px-3 py-1 text-xs font-medium border transition-colors ${(pendingFilters.isActive || "") === opt.value
|
|
113
116
|
? "bg-[var(--appkit-color-primary)] text-white border-[var(--appkit-color-primary)]"
|
|
@@ -3,6 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import React, { useState, useCallback } from "react";
|
|
4
4
|
import { X } from "lucide-react";
|
|
5
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
|
+
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
7
|
+
import { AdminViewCards } from "../../admin/components/AdminViewCards";
|
|
6
8
|
import { FilterChipGroup, ListingToolbar, Pagination, ListingViewShell } from "../../../ui";
|
|
7
9
|
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
10
|
import { SELLER_OFFER_STATUS_TABS } from "../../admin/constants/filter-tabs";
|
|
@@ -18,6 +20,7 @@ const SORT_OPTIONS = [
|
|
|
18
20
|
const STATUS_OPTIONS = SELLER_OFFER_STATUS_TABS;
|
|
19
21
|
export function SellerOffersView({ children, ...props }) {
|
|
20
22
|
const hasChildren = React.Children.count(children) > 0;
|
|
23
|
+
const [view, setView] = useState("table");
|
|
21
24
|
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
22
25
|
const [searchInput, setSearchInput] = useState(table.get("q") || "");
|
|
23
26
|
const [filterOpen, setFilterOpen] = useState(false);
|
|
@@ -72,8 +75,9 @@ export function SellerOffersView({ children, ...props }) {
|
|
|
72
75
|
});
|
|
73
76
|
const currentPage = table.getNumber("page", 1);
|
|
74
77
|
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
78
|
+
const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
|
|
75
79
|
if (hasChildren) {
|
|
76
80
|
return _jsx(ListingViewShell, { portal: "seller", ...props, children: children });
|
|
77
81
|
}
|
|
78
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search offers by product or buyer name", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); },
|
|
82
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search offers by product or buyer name", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No offers received" })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No offers received", onRowClick: undefined, selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }));
|
|
79
83
|
}
|
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import React, { useState, useCallback } from "react";
|
|
4
4
|
import { X, Eye } from "lucide-react";
|
|
5
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
|
+
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
6
7
|
import { Badge, Button, Div, FilterChipGroup, Heading, Input, ListingToolbar, Pagination, ListingViewShell, Select, SideDrawer, Stack, Text } from "../../../ui";
|
|
7
8
|
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
9
|
import { SELLER_ORDER_STATUS_TABS } from "../../admin/constants/filter-tabs";
|
|
@@ -108,6 +109,7 @@ function OrderDetailDrawer({ orderId, apiBase, onClose, }) {
|
|
|
108
109
|
// ---------------------------------------------------------------------------
|
|
109
110
|
export function SellerOrdersView({ orderDetailApiBase = SELLER_ENDPOINTS.ORDERS, children, ...props }) {
|
|
110
111
|
const hasChildren = React.Children.count(children) > 0;
|
|
112
|
+
const [view, setView] = useState("table");
|
|
111
113
|
const [selectedOrderId, setSelectedOrderId] = useState(null);
|
|
112
114
|
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
113
115
|
const [searchInput, setSearchInput] = useState(table.get("q") || "");
|
|
@@ -190,8 +192,9 @@ export function SellerOrdersView({ orderDetailApiBase = SELLER_ENDPOINTS.ORDERS,
|
|
|
190
192
|
},
|
|
191
193
|
];
|
|
192
194
|
const renderRowActions = useCallback((row) => (_jsx("button", { type: "button", onClick: () => setSelectedOrderId(row.id), title: "View order details", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 hover:text-zinc-700 dark:hover:text-zinc-300 transition-colors", children: _jsx(Eye, { className: "h-4 w-4" }) })), []);
|
|
195
|
+
const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
|
|
193
196
|
if (hasChildren) {
|
|
194
197
|
return _jsx(ListingViewShell, { portal: "seller", ...props, children: children });
|
|
195
198
|
}
|
|
196
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by order ID or buyer name", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); },
|
|
199
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by order ID or buyer name", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Div, { className: "mb-4 rounded-xl border border-red-200 bg-red-50 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, columns: columns, isLoading: isLoading, emptyLabel: "No orders yet", renderRowActions: renderRowActions })] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-[var(--appkit-color-primary)] py-2.5 text-sm font-semibold text-white transition-colors active:scale-[0.98]", children: ["Apply", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] })), selectedOrderId && (_jsx(OrderDetailDrawer, { orderId: selectedOrderId, apiBase: orderDetailApiBase, onClose: () => setSelectedOrderId(null) }))] }));
|
|
197
200
|
}
|
|
@@ -3,6 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import React, { useState, useCallback } from "react";
|
|
4
4
|
import { X } from "lucide-react";
|
|
5
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
|
+
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
7
|
+
import { AdminViewCards } from "../../admin/components/AdminViewCards";
|
|
6
8
|
import { FilterChipGroup, ListingToolbar, Pagination, ListingViewShell } from "../../../ui";
|
|
7
9
|
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
10
|
import { ADMIN_PAYOUT_STATUS_TABS } from "../../admin/constants/filter-tabs";
|
|
@@ -18,6 +20,7 @@ const SORT_OPTIONS = [
|
|
|
18
20
|
const STATUS_OPTIONS = ADMIN_PAYOUT_STATUS_TABS;
|
|
19
21
|
export function SellerPayoutsView({ children, ...props }) {
|
|
20
22
|
const hasChildren = React.Children.count(children) > 0;
|
|
23
|
+
const [view, setView] = useState("table");
|
|
21
24
|
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
22
25
|
const [searchInput, setSearchInput] = useState(table.get("q") || "");
|
|
23
26
|
const [filterOpen, setFilterOpen] = useState(false);
|
|
@@ -72,8 +75,9 @@ export function SellerPayoutsView({ children, ...props }) {
|
|
|
72
75
|
});
|
|
73
76
|
const currentPage = table.getNumber("page", 1);
|
|
74
77
|
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
78
|
+
const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
|
|
75
79
|
if (hasChildren) {
|
|
76
80
|
return _jsx(ListingViewShell, { portal: "seller", ...props, children: children });
|
|
77
81
|
}
|
|
78
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search payouts by payout # or amount", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); },
|
|
82
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search payouts by payout # or amount", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No payouts found" })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No payouts found", onRowClick: undefined, selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }));
|
|
79
83
|
}
|
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import React, { useState, useCallback } from "react";
|
|
4
4
|
import { Pencil, X } from "lucide-react";
|
|
5
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
|
+
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
6
7
|
import { Alert, Badge, Button, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell } from "../../../ui";
|
|
7
8
|
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
9
|
import { SELLER_PRE_ORDER_STATUS_TABS } from "../../admin/constants/filter-tabs";
|
|
@@ -70,6 +71,7 @@ const PRE_ORDER_COLUMNS = [
|
|
|
70
71
|
];
|
|
71
72
|
export function SellerPreOrdersView({ children, ...props }) {
|
|
72
73
|
const hasChildren = React.Children.count(children) > 0;
|
|
74
|
+
const [view, setView] = useState("table");
|
|
73
75
|
const dispatch = useActionDispatch();
|
|
74
76
|
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
75
77
|
const [searchInput, setSearchInput] = useState(table.get("q") || "");
|
|
@@ -131,10 +133,11 @@ export function SellerPreOrdersView({ children, ...props }) {
|
|
|
131
133
|
});
|
|
132
134
|
const currentPage = table.getNumber("page", 1);
|
|
133
135
|
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
136
|
+
const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
|
|
134
137
|
if (hasChildren) {
|
|
135
138
|
return (_jsx(ListingViewShell, { portal: "seller", ...props, children: children }));
|
|
136
139
|
}
|
|
137
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search pre-orders by name\u2026", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); },
|
|
140
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search pre-orders by name\u2026", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky z-10 flex justify-center bg-[var(--appkit-color-surface)]/95 backdrop-blur-sm border-b border-[var(--appkit-color-border)] px-3 py-1.5", style: { top: "calc(var(--header-height, 0px) + 44px)" }, children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Alert, { variant: "error", className: "mb-4", children: errorMessage })), _jsx(DataTable, { columns: PRE_ORDER_COLUMNS, rows: rows, isLoading: isLoading, emptyLabel: "No pre-orders listed yet", getRowHref: (row) => String(ROUTES.STORE.PRE_ORDERS_EDIT(row.id)), renderRowActions: (row) => (_jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => {
|
|
138
141
|
e.stopPropagation();
|
|
139
142
|
void dispatch({ type: "NAVIGATE", href: String(ROUTES.STORE.PRE_ORDERS_EDIT(row.id)) });
|
|
140
143
|
}, "aria-label": "Edit", children: _jsx(Pencil, { className: "w-4 h-4" }) })) })] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-[var(--appkit-color-surface)] shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-[var(--appkit-color-border)] px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-[var(--appkit-color-text)]", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-error)] transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-border-subtle)] transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _jsx("div", { className: "border-t border-[var(--appkit-color-border)] px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-[var(--appkit-color-primary)] py-2.5 text-sm font-semibold text-white hover:opacity-90 transition-opacity active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }));
|
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import React, { useState, useCallback } from "react";
|
|
4
4
|
import { Pencil, X } from "lucide-react";
|
|
5
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
|
+
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
6
7
|
import { Alert, Badge, Button, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell } from "../../../ui";
|
|
7
8
|
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
9
|
import { SELLER_PRIZE_DRAW_STATUS_TABS } from "../../admin/constants/filter-tabs";
|
|
@@ -67,6 +68,7 @@ const PRIZE_DRAW_COLUMNS = [
|
|
|
67
68
|
];
|
|
68
69
|
export function SellerPrizeDrawsView({ children, ...props }) {
|
|
69
70
|
const hasChildren = React.Children.count(children) > 0;
|
|
71
|
+
const [view, setView] = useState("table");
|
|
70
72
|
const dispatch = useActionDispatch();
|
|
71
73
|
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
72
74
|
const [searchInput, setSearchInput] = useState(table.get("q") || "");
|
|
@@ -128,10 +130,11 @@ export function SellerPrizeDrawsView({ children, ...props }) {
|
|
|
128
130
|
});
|
|
129
131
|
const currentPage = table.getNumber("page", 1);
|
|
130
132
|
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
133
|
+
const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
|
|
131
134
|
if (hasChildren) {
|
|
132
135
|
return (_jsx(ListingViewShell, { portal: "seller", ...props, children: children }));
|
|
133
136
|
}
|
|
134
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search prize draws by name\u2026", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); },
|
|
137
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search prize draws by name\u2026", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky z-10 flex justify-center bg-[var(--appkit-color-surface)]/95 backdrop-blur-sm border-b border-[var(--appkit-color-border)] px-3 py-1.5", style: { top: "calc(var(--header-height, 0px) + 44px)" }, children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Alert, { variant: "error", className: "mb-4", children: errorMessage })), _jsx(DataTable, { columns: PRIZE_DRAW_COLUMNS, rows: rows, isLoading: isLoading, emptyLabel: "No prize draws listed yet", getRowHref: (row) => String(ROUTES.STORE.PRIZE_DRAWS_EDIT(row.id)), renderRowActions: (row) => (_jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => {
|
|
135
138
|
e.stopPropagation();
|
|
136
139
|
void dispatch({ type: "NAVIGATE", href: String(ROUTES.STORE.PRIZE_DRAWS_EDIT(row.id)) });
|
|
137
140
|
}, "aria-label": "Edit", children: _jsx(Pencil, { className: "w-4 h-4" }) })) })] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-[var(--appkit-color-surface)] shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-[var(--appkit-color-border)] px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-[var(--appkit-color-text)]", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-error)] transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-border-subtle)] transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _jsx("div", { className: "border-t border-[var(--appkit-color-border)] px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-[var(--appkit-color-primary)] py-2.5 text-sm font-semibold text-white hover:opacity-90 transition-opacity active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }));
|
|
@@ -4,6 +4,7 @@ import React, { useState, useCallback } from "react";
|
|
|
4
4
|
import { useActionDispatch } from "../../../react/hooks/use-action-dispatch";
|
|
5
5
|
import { X, Pencil, Trash2 } from "lucide-react";
|
|
6
6
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
7
|
+
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
7
8
|
import { Alert, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, Badge, Button } from "../../../ui";
|
|
8
9
|
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
10
|
import { SELLER_PRODUCT_STATUS_TABS } from "../../admin/constants/filter-tabs";
|
|
@@ -86,6 +87,7 @@ const PRODUCT_COLUMNS = [
|
|
|
86
87
|
];
|
|
87
88
|
export function SellerProductsView({ onDeleteProduct, children, ...props }) {
|
|
88
89
|
const hasChildren = React.Children.count(children) > 0;
|
|
90
|
+
const [view, setView] = useState("table");
|
|
89
91
|
const dispatch = useActionDispatch();
|
|
90
92
|
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
91
93
|
const [searchInput, setSearchInput] = useState(table.get("q") || "");
|
|
@@ -175,6 +177,7 @@ export function SellerProductsView({ onDeleteProduct, children, ...props }) {
|
|
|
175
177
|
});
|
|
176
178
|
const currentPage = table.getNumber("page", 1);
|
|
177
179
|
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
180
|
+
const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
|
|
178
181
|
if (hasChildren) {
|
|
179
182
|
return (_jsx(ListingViewShell, { portal: "seller", ...props, children: children }));
|
|
180
183
|
}
|
|
@@ -201,7 +204,7 @@ export function SellerProductsView({ onDeleteProduct, children, ...props }) {
|
|
|
201
204
|
setDeletingId(null);
|
|
202
205
|
}
|
|
203
206
|
};
|
|
204
|
-
return (_jsx(_Fragment, { children: _jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products by name\u2026", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); },
|
|
207
|
+
return (_jsx(_Fragment, { children: _jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products by name\u2026", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), _jsx(TypeChips, { active: listingKind, onChange: handleKindChange }), totalPages > 1 && (_jsx("div", { className: "sticky z-10 flex justify-center bg-[var(--appkit-color-surface)]/95 backdrop-blur-sm border-b border-[var(--appkit-color-border)] px-3 py-1.5", style: { top: "calc(var(--header-height, 0px) + 44px)" }, children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Alert, { variant: "error", className: "mb-4", children: errorMessage })), _jsx(DataTable, { columns: PRODUCT_COLUMNS, rows: rows, isLoading: isLoading, emptyLabel: listingKind !== "all"
|
|
205
208
|
? `No ${listingKind} listings found`
|
|
206
209
|
: "No products listed yet", getRowHref: (row) => row.listingKind === "auction"
|
|
207
210
|
? String(ROUTES.STORE.AUCTIONS_EDIT(row.id))
|
|
@@ -89,8 +89,8 @@ export declare const sellerStoreSchema: z.ZodObject<{
|
|
|
89
89
|
createdAt: string;
|
|
90
90
|
updatedAt: string;
|
|
91
91
|
status: "pending" | "active" | "rejected" | "suspended";
|
|
92
|
-
isPublic: boolean;
|
|
93
92
|
storeName: string;
|
|
93
|
+
isPublic: boolean;
|
|
94
94
|
storeSlug: string;
|
|
95
95
|
ownerId: string;
|
|
96
96
|
location?: string | undefined;
|
|
@@ -102,17 +102,17 @@ export declare const sellerStoreSchema: z.ZodObject<{
|
|
|
102
102
|
} | undefined;
|
|
103
103
|
returnPolicy?: string | undefined;
|
|
104
104
|
website?: string | undefined;
|
|
105
|
+
storeDescription?: string | undefined;
|
|
106
|
+
storeLogoURL?: string | undefined;
|
|
107
|
+
storeBannerURL?: string | undefined;
|
|
108
|
+
bio?: string | undefined;
|
|
105
109
|
socialLinks?: {
|
|
106
110
|
instagram?: string | undefined;
|
|
107
111
|
facebook?: string | undefined;
|
|
108
112
|
twitter?: string | undefined;
|
|
109
113
|
linkedin?: string | undefined;
|
|
110
114
|
} | undefined;
|
|
111
|
-
storeDescription?: string | undefined;
|
|
112
115
|
storeCategory?: string | undefined;
|
|
113
|
-
storeLogoURL?: string | undefined;
|
|
114
|
-
storeBannerURL?: string | undefined;
|
|
115
|
-
bio?: string | undefined;
|
|
116
116
|
shippingPolicy?: string | undefined;
|
|
117
117
|
isVacationMode?: boolean | undefined;
|
|
118
118
|
vacationMessage?: string | undefined;
|
|
@@ -121,8 +121,8 @@ export declare const sellerStoreSchema: z.ZodObject<{
|
|
|
121
121
|
createdAt: string;
|
|
122
122
|
updatedAt: string;
|
|
123
123
|
status: "pending" | "active" | "rejected" | "suspended";
|
|
124
|
-
isPublic: boolean;
|
|
125
124
|
storeName: string;
|
|
125
|
+
isPublic: boolean;
|
|
126
126
|
storeSlug: string;
|
|
127
127
|
ownerId: string;
|
|
128
128
|
location?: string | undefined;
|
|
@@ -134,17 +134,17 @@ export declare const sellerStoreSchema: z.ZodObject<{
|
|
|
134
134
|
} | undefined;
|
|
135
135
|
returnPolicy?: string | undefined;
|
|
136
136
|
website?: string | undefined;
|
|
137
|
+
storeDescription?: string | undefined;
|
|
138
|
+
storeLogoURL?: string | undefined;
|
|
139
|
+
storeBannerURL?: string | undefined;
|
|
140
|
+
bio?: string | undefined;
|
|
137
141
|
socialLinks?: {
|
|
138
142
|
instagram?: string | undefined;
|
|
139
143
|
facebook?: string | undefined;
|
|
140
144
|
twitter?: string | undefined;
|
|
141
145
|
linkedin?: string | undefined;
|
|
142
146
|
} | undefined;
|
|
143
|
-
storeDescription?: string | undefined;
|
|
144
147
|
storeCategory?: string | undefined;
|
|
145
|
-
storeLogoURL?: string | undefined;
|
|
146
|
-
storeBannerURL?: string | undefined;
|
|
147
|
-
bio?: string | undefined;
|
|
148
148
|
shippingPolicy?: string | undefined;
|
|
149
149
|
isVacationMode?: boolean | undefined;
|
|
150
150
|
vacationMessage?: string | undefined;
|
|
@@ -22,6 +22,9 @@ const SAFE_STORE_FILTER_FIELDS = new Set([
|
|
|
22
22
|
"storeCategory",
|
|
23
23
|
"status",
|
|
24
24
|
"isPublic",
|
|
25
|
+
"isFeatured",
|
|
26
|
+
"averageRating",
|
|
27
|
+
"stats.totalProducts",
|
|
25
28
|
]);
|
|
26
29
|
function validateSieveFilters(raw, allowedFields) {
|
|
27
30
|
return raw
|
|
@@ -40,7 +43,13 @@ export async function GET(request) {
|
|
|
40
43
|
const std = parseListingParams(url);
|
|
41
44
|
const page = std.page ?? DEFAULT_PAGE;
|
|
42
45
|
const pageSize = std.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
43
|
-
|
|
46
|
+
// Translate user-friendly sort names to Firestore nested field paths.
|
|
47
|
+
const rawSort = std.sorts ?? DEFAULT_SORT;
|
|
48
|
+
const sort = rawSort
|
|
49
|
+
.replace(/^-itemsSold$/, "-stats.itemsSold")
|
|
50
|
+
.replace(/^itemsSold$/, "stats.itemsSold")
|
|
51
|
+
.replace(/^-averageRating$/, "-stats.averageRating")
|
|
52
|
+
.replace(/^averageRating$/, "stats.averageRating");
|
|
44
53
|
const parts = ["status==active,isPublic==true"];
|
|
45
54
|
if (std.q)
|
|
46
55
|
parts.push(`storeName@=*${std.q}`);
|
|
@@ -25,5 +25,5 @@ export function StoreReviewsListing({ storeSlug }) {
|
|
|
25
25
|
: "border-zinc-200 text-zinc-600 hover:bg-zinc-50 dark:border-zinc-700 dark:text-zinc-300",
|
|
26
26
|
].join(" "), children: opt.label }, opt.value))) }), isLoading ? (_jsx(Stack, { align: "center", gap: "3", className: "justify-center py-12", children: _jsx(Text, { className: "text-zinc-500", children: "Loading reviews..." }) })) : paginated.length === 0 ? (_jsxs(Stack, { align: "center", gap: "3", className: "justify-center py-24 text-center", children: [_jsx(Text, { className: "text-xl font-medium text-zinc-900 dark:text-zinc-50", children: "No reviews yet" }), _jsx(Text, { className: "text-sm text-zinc-500", children: ratingFilter > 0
|
|
27
27
|
? `No ${ratingFilter}-star reviews yet.`
|
|
28
|
-
: "Be the first to review this store by purchasing a product." })] })) : (_jsxs(_Fragment, { children: [_jsx(Grid, { cols: 3, gap: "md", children: paginated.map((review) => (_jsx(ReviewCard, { review: review }, review.id))) }), totalPages > 1 && (_jsx(Row, { className: "mt-8 justify-center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: setPage }) }))] }))] }) }));
|
|
28
|
+
: "Be the first to review this store by purchasing a product." })] })) : (_jsxs(_Fragment, { children: [_jsx(Grid, { cols: 3, gap: "md", children: paginated.map((review) => (_jsx(ReviewCard, { review: review, context: "store" }, review.id))) }), totalPages > 1 && (_jsx(Row, { className: "mt-8 justify-center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: setPage }) }))] }))] }) }));
|
|
29
29
|
}
|
|
@@ -9,23 +9,27 @@ import { ROUTES } from "../../../next";
|
|
|
9
9
|
import { InteractiveStoreCard } from "./InteractiveStoreCard";
|
|
10
10
|
import { StoreFilters } from "./StoreFilters";
|
|
11
11
|
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
12
|
+
import { TABLE_KEYS, VIEW_MODE } from "../../../constants/table-keys";
|
|
13
|
+
import { sortBy } from "../../../constants/sort";
|
|
14
|
+
import { STORE_FIELDS } from "../../../constants/field-names";
|
|
15
|
+
const DEFAULT_SORT = sortBy(STORE_FIELDS.CREATED_AT);
|
|
12
16
|
const STORE_SORT_OPTIONS = [
|
|
13
|
-
{ value:
|
|
14
|
-
{ value: "
|
|
17
|
+
{ value: sortBy(STORE_FIELDS.CREATED_AT), label: "Newest First" },
|
|
18
|
+
{ value: sortBy(STORE_FIELDS.STORE_NAME, "ASC"), label: "Name A–Z" },
|
|
15
19
|
{ value: "-itemsSold", label: "Most Sales" },
|
|
16
20
|
{ value: "-averageRating", label: "Top Rated" },
|
|
17
21
|
];
|
|
18
|
-
const FILTER_KEYS = [
|
|
22
|
+
const FILTER_KEYS = [TABLE_KEYS.CATEGORY, "rating", "minProductCount", "maxProductCount", TABLE_KEYS.FEATURED];
|
|
19
23
|
export function StoresIndexListing({ initialData }) {
|
|
20
|
-
const table = useUrlTable({ defaults: { pageSize: "24", sort:
|
|
21
|
-
const [searchInput, setSearchInput] = useState(table.get(
|
|
24
|
+
const table = useUrlTable({ defaults: { pageSize: "24", sort: DEFAULT_SORT } });
|
|
25
|
+
const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
|
|
22
26
|
const [filterOpen, setFilterOpen] = useState(false);
|
|
23
|
-
const [view, setView] = useState(table.get(
|
|
27
|
+
const [view, setView] = useState(table.get(TABLE_KEYS.VIEW) || VIEW_MODE.GRID);
|
|
24
28
|
const handleViewToggle = (next) => {
|
|
25
|
-
if (next ===
|
|
29
|
+
if (next === VIEW_MODE.TABLE)
|
|
26
30
|
return;
|
|
27
31
|
setView(next);
|
|
28
|
-
table.set(
|
|
32
|
+
table.set(TABLE_KEYS.VIEW, next);
|
|
29
33
|
};
|
|
30
34
|
// Pending filter state — buffered until "Apply Filters" clicked
|
|
31
35
|
const [pendingFilters, setPendingFilters] = useState(() => Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
|
|
@@ -69,24 +73,24 @@ export function StoresIndexListing({ initialData }) {
|
|
|
69
73
|
setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, ""])));
|
|
70
74
|
}, []);
|
|
71
75
|
const resetAll = useCallback(() => {
|
|
72
|
-
const updates = {
|
|
76
|
+
const updates = { [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "" };
|
|
73
77
|
for (const k of FILTER_KEYS)
|
|
74
78
|
updates[k] = "";
|
|
75
79
|
table.setMany(updates);
|
|
76
80
|
setSearchInput("");
|
|
77
81
|
}, [table]);
|
|
78
82
|
const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
|
|
79
|
-
const hasActiveState = !!table.get(
|
|
80
|
-
table.get(
|
|
83
|
+
const hasActiveState = !!table.get(TABLE_KEYS.QUERY) ||
|
|
84
|
+
table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT ||
|
|
81
85
|
activeFilterCount > 0;
|
|
82
86
|
const commitSearch = useCallback(() => {
|
|
83
|
-
table.set(
|
|
87
|
+
table.set(TABLE_KEYS.QUERY, searchInput.trim());
|
|
84
88
|
}, [searchInput, table]);
|
|
85
89
|
// Build sieve filters from applied URL params
|
|
86
|
-
const ratingRaw = table.get(
|
|
90
|
+
const ratingRaw = table.get(TABLE_KEYS.RATING);
|
|
87
91
|
const minProductCount = table.get("minProductCount");
|
|
88
92
|
const maxProductCount = table.get("maxProductCount");
|
|
89
|
-
const featured = table.get(
|
|
93
|
+
const featured = table.get(TABLE_KEYS.FEATURED);
|
|
90
94
|
const filterParts = [];
|
|
91
95
|
if (ratingRaw) {
|
|
92
96
|
const ratings = ratingRaw.split("|").filter(Boolean);
|
|
@@ -100,15 +104,15 @@ export function StoresIndexListing({ initialData }) {
|
|
|
100
104
|
if (featured === "true")
|
|
101
105
|
filterParts.push("isFeatured==true");
|
|
102
106
|
const { stores, totalPages, isLoading } = useStores({
|
|
103
|
-
q: table.get(
|
|
104
|
-
page: table.getNumber(
|
|
105
|
-
pageSize: table.getNumber(
|
|
106
|
-
sort: table.get(
|
|
107
|
-
category: table.get(
|
|
107
|
+
q: table.get(TABLE_KEYS.QUERY) || undefined,
|
|
108
|
+
page: table.getNumber(TABLE_KEYS.PAGE, 1),
|
|
109
|
+
pageSize: table.getNumber(TABLE_KEYS.PAGE_SIZE, 24),
|
|
110
|
+
sort: table.get(TABLE_KEYS.SORT) || undefined,
|
|
111
|
+
category: table.get(TABLE_KEYS.CATEGORY) || undefined,
|
|
108
112
|
filters: filterParts.length > 0 ? filterParts.join(",") : undefined,
|
|
109
113
|
}, { initialData });
|
|
110
114
|
const selection = useBulkSelection({ items: stores, keyExtractor: (s) => s.id ?? s.storeSlug });
|
|
111
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search stores...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(
|
|
115
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search stores...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: STORE_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: stores.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
|
|
112
116
|
{
|
|
113
117
|
id: "compare",
|
|
114
118
|
label: "Compare",
|