@mohasinac/appkit 2.7.53 → 2.7.55
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/client/features/layout/DashboardLayoutClient.js +1 -1
- package/dist/_internal/server/jobs/core/auctionSettlement.js +6 -3
- package/dist/_internal/server/jobs/core/bundleStockSync.js +9 -7
- package/dist/_internal/server/jobs/core/onProductStockChange.d.ts +12 -3
- package/dist/_internal/server/jobs/core/onProductStockChange.js +12 -13
- package/dist/_internal/server/jobs/core/onProductWrite.js +1 -3
- package/dist/_internal/server/jobs/handlers/onProductStockChange.js +11 -2
- package/dist/_internal/server/jobs/runtime/adapters/firebase.js +5 -14
- package/dist/_internal/shared/actions/action-registry.js +827 -4
- package/dist/_internal/shared/features/events/schema.d.ts +4 -4
- package/dist/client.d.ts +2 -0
- package/dist/client.js +1 -0
- package/dist/constants/api-endpoints.d.ts +3 -0
- package/dist/constants/api-endpoints.js +1 -0
- package/dist/constants/field-names.d.ts +4 -3
- package/dist/constants/field-names.js +8 -8
- package/dist/errors/error-codes.d.ts +3 -0
- package/dist/errors/error-codes.js +3 -0
- package/dist/errors/messages.d.ts +3 -0
- package/dist/errors/messages.js +3 -0
- package/dist/features/account/components/UserOffersPanel.js +2 -1
- package/dist/features/admin/components/AdminAllEventEntriesView.js +4 -3
- package/dist/features/admin/components/AdminBidsView.js +5 -3
- package/dist/features/admin/components/AdminBlogView.js +3 -2
- package/dist/features/admin/components/AdminBrandsView.js +2 -1
- package/dist/features/admin/components/AdminBundleEditorView.js +4 -5
- package/dist/features/admin/components/AdminBundlesView.js +3 -3
- package/dist/features/admin/components/AdminCarouselView.d.ts +2 -1
- package/dist/features/admin/components/AdminCarouselView.js +14 -5
- package/dist/features/admin/components/AdminCategoriesView.js +2 -1
- package/dist/features/admin/components/AdminContactView.d.ts +4 -1
- package/dist/features/admin/components/AdminContactView.js +27 -7
- package/dist/features/admin/components/AdminCouponsView.d.ts +3 -1
- package/dist/features/admin/components/AdminCouponsView.js +17 -3
- package/dist/features/admin/components/AdminFaqsView.d.ts +3 -1
- package/dist/features/admin/components/AdminFaqsView.js +18 -4
- package/dist/features/admin/components/AdminFeaturesView.js +2 -1
- package/dist/features/admin/components/AdminMediaView.js +3 -2
- package/dist/features/admin/components/AdminNavigationView.js +3 -2
- package/dist/features/admin/components/AdminNewsletterView.d.ts +2 -1
- package/dist/features/admin/components/AdminNewsletterView.js +13 -4
- package/dist/features/admin/components/AdminNotificationsView.js +6 -4
- package/dist/features/admin/components/AdminOrdersView.js +4 -3
- package/dist/features/admin/components/AdminPayoutsView.js +2 -2
- package/dist/features/admin/components/AdminPrizeDrawsView.js +2 -1
- package/dist/features/admin/components/AdminProductsView.js +3 -3
- package/dist/features/admin/components/AdminReviewsView.js +4 -3
- package/dist/features/admin/components/AdminScammersView.js +2 -1
- package/dist/features/admin/components/AdminSessionsView.js +5 -3
- package/dist/features/admin/components/AdminStoresView.js +2 -2
- package/dist/features/admin/components/AdminSublistingCategoriesView.d.ts +5 -1
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +12 -3
- package/dist/features/admin/components/AdminSupportTicketsView.js +2 -1
- package/dist/features/admin/components/AdminTeamView.d.ts +2 -1
- package/dist/features/admin/components/AdminTeamView.js +13 -4
- package/dist/features/admin/components/AdminUsersView.js +2 -2
- package/dist/features/admin/types/product.types.d.ts +1 -1
- package/dist/features/admin/types/product.types.js +2 -3
- package/dist/features/auctions/actions/bid-actions.d.ts +9 -0
- package/dist/features/auctions/actions/bid-actions.js +61 -0
- package/dist/features/auctions/components/AuctionDetailPageView.d.ts +2 -1
- package/dist/features/auctions/components/AuctionDetailPageView.js +5 -4
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +3 -1
- package/dist/features/auctions/components/PlaceBidFormClient.js +32 -2
- package/dist/features/auctions/types/index.d.ts +1 -1
- package/dist/features/layout/AppLayoutShell.js +1 -1
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +2 -11
- package/dist/features/products/api/[id]/route.js +4 -8
- package/dist/features/products/api/route.js +1 -3
- package/dist/features/products/components/AuctionsIndexListing.js +3 -11
- package/dist/features/products/components/PrizeDrawsIndexListing.js +2 -10
- package/dist/features/products/components/ProductFilters.d.ts +33 -9
- package/dist/features/products/components/ProductFilters.js +6 -29
- package/dist/features/products/components/ProductForm.js +2 -3
- package/dist/features/products/constants/action-defs.d.ts +86 -7
- package/dist/features/products/constants/action-defs.js +99 -7
- package/dist/features/products/constants/sieve.d.ts +162 -0
- package/dist/features/products/constants/sieve.js +95 -0
- package/dist/features/products/repository/products.repository.d.ts +5 -0
- package/dist/features/products/repository/products.repository.js +23 -3
- package/dist/features/products/schemas/firestore.d.ts +4 -4
- package/dist/features/products/schemas/firestore.js +2 -3
- package/dist/features/products/schemas/index.d.ts +6 -6
- package/dist/features/products/schemas/index.js +2 -6
- package/dist/features/products/types/index.d.ts +1 -1
- package/dist/features/seller/actions/offer-actions.js +2 -2
- package/dist/features/seller/components/QuickProductForm.d.ts +13 -0
- package/dist/features/seller/components/QuickProductForm.js +50 -0
- package/dist/features/seller/components/SellerAuctionsView.d.ts +4 -1
- package/dist/features/seller/components/SellerAuctionsView.js +37 -3
- package/dist/features/seller/components/SellerBidsView.js +2 -1
- package/dist/features/seller/components/SellerBundlesView.d.ts +3 -1
- package/dist/features/seller/components/SellerBundlesView.js +38 -5
- package/dist/features/seller/components/SellerClassifiedView.d.ts +3 -1
- package/dist/features/seller/components/SellerClassifiedView.js +37 -4
- package/dist/features/seller/components/SellerDigitalCodesView.d.ts +3 -1
- package/dist/features/seller/components/SellerDigitalCodesView.js +37 -4
- package/dist/features/seller/components/SellerGoogleReviewsView.js +2 -1
- package/dist/features/seller/components/SellerGroupedListingsView.js +3 -2
- package/dist/features/seller/components/SellerLiveView.d.ts +3 -1
- package/dist/features/seller/components/SellerLiveView.js +37 -4
- package/dist/features/seller/components/SellerOffersPanel.js +2 -1
- package/dist/features/seller/components/SellerOffersView.d.ts +4 -1
- package/dist/features/seller/components/SellerOffersView.js +8 -3
- package/dist/features/seller/components/SellerPayoutsView.d.ts +3 -1
- package/dist/features/seller/components/SellerPayoutsView.js +7 -3
- package/dist/features/seller/components/SellerPreOrdersView.d.ts +2 -1
- package/dist/features/seller/components/SellerPreOrdersView.js +30 -7
- package/dist/features/seller/components/SellerPrizeDrawsView.d.ts +2 -1
- package/dist/features/seller/components/SellerPrizeDrawsView.js +30 -7
- package/dist/features/seller/components/SellerProductShell.js +12 -0
- package/dist/features/seller/components/SellerReviewsView.js +2 -1
- package/dist/features/seller/components/SellerShippingConfigsView.js +3 -2
- package/dist/features/seller/components/SellerStoreCategoriesView.js +6 -5
- package/dist/features/seller/components/SellerTemplatesView.js +4 -3
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +30 -16
- package/dist/features/wishlist/types/index.d.ts +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.js +6 -4
- package/dist/seed/addresses-seed-data.d.ts +2 -18
- package/dist/seed/addresses-seed-data.js +137 -203
- package/dist/seed/bids-seed-data.d.ts +2 -8
- package/dist/seed/bids-seed-data.js +172 -648
- package/dist/seed/blog-posts-seed-data.d.ts +0 -5
- package/dist/seed/blog-posts-seed-data.js +341 -596
- package/dist/seed/carousel-slides-seed-data.d.ts +0 -5
- package/dist/seed/carousel-slides-seed-data.js +61 -192
- package/dist/seed/carousels-seed-data.js +18 -1
- package/dist/seed/cart-seed-data.d.ts +1 -18
- package/dist/seed/cart-seed-data.js +116 -293
- package/dist/seed/categories-seed-data.d.ts +0 -6
- package/dist/seed/categories-seed-data.js +1409 -1537
- package/dist/seed/claimed-coupons-seed-data.d.ts +0 -11
- package/dist/seed/claimed-coupons-seed-data.js +32 -29
- package/dist/seed/conversations-seed-data.d.ts +0 -6
- package/dist/seed/conversations-seed-data.js +132 -280
- package/dist/seed/coupon-usage-seed-data.d.ts +0 -16
- package/dist/seed/coupon-usage-seed-data.js +30 -44
- package/dist/seed/coupons-seed-data.d.ts +0 -6
- package/dist/seed/coupons-seed-data.js +141 -489
- package/dist/seed/events-seed-data.d.ts +0 -6
- package/dist/seed/events-seed-data.js +201 -379
- package/dist/seed/faq-seed-data.d.ts +0 -14
- package/dist/seed/faq-seed-data.js +609 -1184
- package/dist/seed/grouped-listings-seed-data.d.ts +119 -8
- package/dist/seed/grouped-listings-seed-data.js +148 -233
- package/dist/seed/history-seed-data.d.ts +0 -7
- package/dist/seed/history-seed-data.js +50 -54
- package/dist/seed/homepage-sections-seed-data.d.ts +0 -5
- package/dist/seed/homepage-sections-seed-data.js +83 -175
- package/dist/seed/index.d.ts +1 -1
- package/dist/seed/index.js +1 -1
- package/dist/seed/manifest.js +2 -2
- package/dist/seed/notifications-seed-data.d.ts +0 -6
- package/dist/seed/notifications-seed-data.js +309 -196
- package/dist/seed/offers-seed-data.d.ts +0 -8
- package/dist/seed/offers-seed-data.js +118 -197
- package/dist/seed/orders-seed-data.d.ts +2 -8
- package/dist/seed/orders-seed-data.js +248 -1510
- package/dist/seed/payouts-seed-data.d.ts +0 -6
- package/dist/seed/payouts-seed-data.js +102 -441
- package/dist/seed/products-auctions-seed-data.d.ts +1 -14
- package/dist/seed/products-auctions-seed-data.js +612 -1084
- package/dist/seed/products-preorders-seed-data.d.ts +119 -14
- package/dist/seed/products-preorders-seed-data.js +154 -455
- package/dist/seed/products-standard-seed-data.d.ts +1 -15
- package/dist/seed/products-standard-seed-data.js +1355 -4128
- package/dist/seed/reviews-seed-data.d.ts +2 -8
- package/dist/seed/reviews-seed-data.js +99 -1182
- package/dist/seed/scammers-seed-data.d.ts +0 -15
- package/dist/seed/scammers-seed-data.js +36 -37
- package/dist/seed/sessions-seed-data.d.ts +0 -13
- package/dist/seed/sessions-seed-data.js +58 -335
- package/dist/seed/site-settings-seed-data.d.ts +0 -4
- package/dist/seed/site-settings-seed-data.js +61 -52
- package/dist/seed/store-addresses-seed-data.js +27 -151
- package/dist/seed/store-extensions-seed-data.d.ts +0 -7
- package/dist/seed/store-extensions-seed-data.js +147 -261
- package/dist/seed/stores-seed-data.d.ts +0 -5
- package/dist/seed/stores-seed-data.js +222 -184
- package/dist/seed/sub-listings-seed-data.d.ts +119 -0
- package/dist/seed/sub-listings-seed-data.js +447 -0
- package/dist/seed/support-tickets-seed-data.d.ts +0 -7
- package/dist/seed/support-tickets-seed-data.js +82 -75
- package/dist/seed/users-seed-data.d.ts +0 -5
- package/dist/seed/users-seed-data.js +279 -634
- package/dist/seed/wishlists-seed-data.d.ts +0 -9
- package/dist/seed/wishlists-seed-data.js +36 -52
- package/dist/seo/json-ld.js +6 -4
- package/dist/styles.css +8052 -3
- package/dist/tailwind-utilities.css +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/search-tokens.d.ts +2 -0
- package/dist/utils/search-tokens.js +21 -0
- package/package.json +2 -2
- package/scripts/seed-cli.mjs +2 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ListingViewShellProps } from "../../../ui";
|
|
2
2
|
export interface AdminTeamViewProps extends ListingViewShellProps {
|
|
3
|
+
onBulkRemove?: (ids: string[]) => Promise<void>;
|
|
3
4
|
}
|
|
4
|
-
export declare function AdminTeamView({ children, ...props }: AdminTeamViewProps): import("react/jsx-runtime").JSX.Element;
|
|
5
|
+
export declare function AdminTeamView({ children, onBulkRemove, ...props }: AdminTeamViewProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -4,8 +4,9 @@ import React, { useState, useCallback } from "react";
|
|
|
4
4
|
import { X, UserPlus } from "lucide-react";
|
|
5
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
6
|
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
7
|
-
import { Button, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, RowActionMenu, } from "../../../ui";
|
|
7
|
+
import { BulkActionBar, Button, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, RowActionMenu, } from "../../../ui";
|
|
8
8
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
|
+
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
9
10
|
import { ALL_TAB } from "../constants/filter-tabs";
|
|
10
11
|
import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
|
|
11
12
|
import { DataTable } from "./DataTable";
|
|
@@ -48,7 +49,7 @@ function TeamFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, clearF
|
|
|
48
49
|
return null;
|
|
49
50
|
return (_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", children: _jsx(FilterChipGroup, { label: "Permission Group", tabs: GROUP_TABS, value: pendingGroup, onChange: (id) => setPendingGroup(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})` : ""] }) })] })] }));
|
|
50
51
|
}
|
|
51
|
-
export function AdminTeamView({ children, ...props }) {
|
|
52
|
+
export function AdminTeamView({ children, onBulkRemove, ...props }) {
|
|
52
53
|
const hasChildren = React.Children.count(children) > 0;
|
|
53
54
|
const [view, setView] = useState("table");
|
|
54
55
|
const table = useUrlTable({
|
|
@@ -130,14 +131,22 @@ export function AdminTeamView({ children, ...props }) {
|
|
|
130
131
|
const currentPage = table.getNumber("page", 1);
|
|
131
132
|
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
132
133
|
const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
|
|
134
|
+
const bulkActions = [
|
|
135
|
+
...(onBulkRemove ? [{
|
|
136
|
+
id: "bulk-remove",
|
|
137
|
+
label: ACTIONS.ADMIN["remove-team-member"].label,
|
|
138
|
+
variant: "danger",
|
|
139
|
+
onClick: async () => { await onBulkRemove(selection.selectedIds); selection.clearSelection(); },
|
|
140
|
+
}] : []),
|
|
141
|
+
];
|
|
133
142
|
if (hasChildren) {
|
|
134
143
|
return (_jsx(ListingViewShell, { portal: "admin", ...props, children: children }));
|
|
135
144
|
}
|
|
136
145
|
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) => {
|
|
137
146
|
table.set("sort", v);
|
|
138
|
-
}, 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: [
|
|
147
|
+
}, 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"] }) }), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: 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: [
|
|
139
148
|
{
|
|
140
|
-
label: "
|
|
149
|
+
label: ACTIONS.ADMIN["edit-team-member"].label,
|
|
141
150
|
onClick: () => openEdit(row),
|
|
142
151
|
},
|
|
143
152
|
] })) })] }), _jsx(TeamFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingGroup: pendingGroup, setPendingGroup: setPendingGroup, applyFilters: applyFilters })] }), _jsx(AdminEmployeeEditorView, { open: drawerOpen, onClose: () => setDrawerOpen(false), mode: inviteMode ? "invite" : "edit", userId: selectedRow?.id, displayName: selectedRow?.primary, currentPermissionGroup: toStringValue(selectedRow?._raw?.permissionGroup, "custom"), currentPermissions: Array.isArray(selectedRow?._raw?.permissions)
|
|
@@ -147,13 +147,13 @@ export function AdminUsersView({ children, ...props }) {
|
|
|
147
147
|
return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
|
|
148
148
|
}
|
|
149
149
|
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: [
|
|
150
|
-
{ id: "manage", label: "
|
|
150
|
+
{ id: "manage", label: ACTIONS.ADMIN["manage-user"].label, variant: "primary", onClick: () => { setSelectedRow(rows.find(r => r.id === selection.selectedIds[0]) ?? null); setDrawerOpen(true); selection.clearSelection(); } },
|
|
151
151
|
] }), 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) => {
|
|
152
152
|
const ur = row;
|
|
153
153
|
const isBanned = ur.status === "Hard banned";
|
|
154
154
|
return (_jsx(RowActionMenu, { actions: [
|
|
155
155
|
{
|
|
156
|
-
label: "
|
|
156
|
+
label: ACTIONS.ADMIN["manage-user"].label,
|
|
157
157
|
onClick: () => { setSelectedRow(ur); setDrawerOpen(true); },
|
|
158
158
|
},
|
|
159
159
|
{
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* These types extend/complement the core ProductItem with admin-only fields
|
|
5
5
|
* for managing auctions, pre-orders, stock, pricing, and lifecycle.
|
|
6
6
|
*/
|
|
7
|
-
export type AdminProductStatus = "draft" | "published" | "
|
|
7
|
+
export type AdminProductStatus = "draft" | "published" | "in_review" | "archived";
|
|
8
8
|
export type AdminProductDrawerMode = "create" | "edit" | "delete" | null;
|
|
9
9
|
/**
|
|
10
10
|
* Admin product form/management type.
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
export const ADMIN_PRODUCT_STATUS_OPTIONS = [
|
|
8
8
|
{ value: "draft", label: "Draft" },
|
|
9
9
|
{ value: "published", label: "Published" },
|
|
10
|
-
{ value: "
|
|
11
|
-
{ value: "
|
|
12
|
-
{ value: "sold", label: "Sold" },
|
|
10
|
+
{ value: "in_review", label: "In Review" },
|
|
11
|
+
{ value: "archived", label: "Archived" },
|
|
13
12
|
];
|
|
@@ -15,6 +15,15 @@ export interface PlaceBidResult {
|
|
|
15
15
|
bid: BidDocument;
|
|
16
16
|
}
|
|
17
17
|
export declare function placeBid(userId: string, userEmail: string, input: PlaceBidInput): Promise<PlaceBidResult>;
|
|
18
|
+
export interface BuyNowAuctionInput {
|
|
19
|
+
productId: string;
|
|
20
|
+
}
|
|
21
|
+
export interface BuyNowAuctionResult {
|
|
22
|
+
orderId: string;
|
|
23
|
+
amount: number;
|
|
24
|
+
currency: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function buyNowAuction(userId: string, userName: string, userEmail: string, input: BuyNowAuctionInput): Promise<BuyNowAuctionResult>;
|
|
18
27
|
export declare function listBidsByProduct(productId: string, params?: {
|
|
19
28
|
page?: number;
|
|
20
29
|
pageSize?: number;
|
|
@@ -26,6 +26,9 @@ export async function placeBid(userId, userEmail, input) {
|
|
|
26
26
|
throw new NotFoundError(ERROR_MESSAGES.BID.AUCTION_NOT_FOUND);
|
|
27
27
|
if (!isAuctionListing(product))
|
|
28
28
|
throw new ValidationError(ERROR_MESSAGES.BID.NOT_AN_AUCTION);
|
|
29
|
+
if (product.isSold) {
|
|
30
|
+
throw new ValidationError(ERROR_MESSAGES.BID.AUCTION_ENDED, { code: BID_ERROR_CODES.AUCTION_ENDED });
|
|
31
|
+
}
|
|
29
32
|
if (product.auctionEndDate) {
|
|
30
33
|
const endDate = resolveDate(product.auctionEndDate);
|
|
31
34
|
if (endDate && endDate.getTime() < Date.now()) {
|
|
@@ -152,6 +155,64 @@ export async function placeBid(userId, userEmail, input) {
|
|
|
152
155
|
});
|
|
153
156
|
return { bid };
|
|
154
157
|
}
|
|
158
|
+
export async function buyNowAuction(userId, userName, userEmail, input) {
|
|
159
|
+
const { productId } = input;
|
|
160
|
+
const product = await productRepository.findById(productId);
|
|
161
|
+
if (!product)
|
|
162
|
+
throw new NotFoundError(ERROR_MESSAGES.BID.AUCTION_NOT_FOUND);
|
|
163
|
+
if (!isAuctionListing(product))
|
|
164
|
+
throw new ValidationError(ERROR_MESSAGES.BID.NOT_AN_AUCTION);
|
|
165
|
+
if (product.isSold) {
|
|
166
|
+
throw new ValidationError(ERROR_MESSAGES.BID.AUCTION_ENDED, { code: BID_ERROR_CODES.AUCTION_ENDED });
|
|
167
|
+
}
|
|
168
|
+
if (product.auctionEndDate) {
|
|
169
|
+
const endDate = resolveDate(product.auctionEndDate);
|
|
170
|
+
if (endDate && endDate.getTime() < Date.now()) {
|
|
171
|
+
throw new ValidationError(ERROR_MESSAGES.BID.AUCTION_ENDED, { code: BID_ERROR_CODES.AUCTION_ENDED });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (product.storeId) {
|
|
175
|
+
const store = await storeRepository.findById(product.storeId);
|
|
176
|
+
if (store?.ownerId === userId) {
|
|
177
|
+
throw new AuthorizationError(ERROR_MESSAGES.BID.OWN_AUCTION);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const buyNowPrice = typeof product.buyNowPrice === "number" ? product.buyNowPrice : null;
|
|
181
|
+
if (buyNowPrice === null || buyNowPrice <= 0) {
|
|
182
|
+
throw new ValidationError(ERROR_MESSAGES.BID.BUY_NOW_NO_PRICE, { code: BID_ERROR_CODES.BUY_NOW_NO_PRICE });
|
|
183
|
+
}
|
|
184
|
+
if (product.bidsHaveStarted === true) {
|
|
185
|
+
throw new ValidationError(ERROR_MESSAGES.BID.BUY_NOW_BIDS_STARTED, { code: BID_ERROR_CODES.BUY_NOW_BIDS_STARTED });
|
|
186
|
+
}
|
|
187
|
+
const currency = getDefaultCurrency();
|
|
188
|
+
let orderId = "";
|
|
189
|
+
await unitOfWork.runBatch((batch) => {
|
|
190
|
+
const orderRef = unitOfWork.orders.createFromAuction(batch, {
|
|
191
|
+
productId,
|
|
192
|
+
productTitle: product.title,
|
|
193
|
+
userId,
|
|
194
|
+
userName,
|
|
195
|
+
userEmail,
|
|
196
|
+
storeId: product.storeId,
|
|
197
|
+
amount: buyNowPrice,
|
|
198
|
+
currency,
|
|
199
|
+
auctionProductId: productId,
|
|
200
|
+
});
|
|
201
|
+
orderId = orderRef.id;
|
|
202
|
+
unitOfWork.products.updateInBatch(batch, productId, {
|
|
203
|
+
isSold: true,
|
|
204
|
+
availableQuantity: 0,
|
|
205
|
+
auctionEndDate: new Date(),
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
serverLogger.info("buyNowAuction", {
|
|
209
|
+
orderId,
|
|
210
|
+
productId,
|
|
211
|
+
userId,
|
|
212
|
+
amount: buyNowPrice,
|
|
213
|
+
});
|
|
214
|
+
return { orderId, amount: buyNowPrice, currency };
|
|
215
|
+
}
|
|
155
216
|
export async function listBidsByProduct(productId, params) {
|
|
156
217
|
const result = await bidRepository.list({
|
|
157
218
|
filters: `productId==${productId}`,
|
|
@@ -10,7 +10,8 @@ export interface AuctionDetailPageViewProps {
|
|
|
10
10
|
*/
|
|
11
11
|
initialAuction?: import("../../products/schemas/firestore").ProductDocument | null;
|
|
12
12
|
onPlaceBid?: (input: PlaceBidInput) => Promise<unknown>;
|
|
13
|
+
onBuyNow?: () => Promise<unknown>;
|
|
13
14
|
/** SSR-loaded productFeatures (platform + store-scope). See ProductDetailPageView for semantics. */
|
|
14
15
|
productFeatures?: ProductFeatureDocument[];
|
|
15
16
|
}
|
|
16
|
-
export declare function AuctionDetailPageView({ id, initialAuction, onPlaceBid, productFeatures }: AuctionDetailPageViewProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
17
|
+
export declare function AuctionDetailPageView({ id, initialAuction, onPlaceBid, onBuyNow, productFeatures }: AuctionDetailPageViewProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
@@ -40,7 +40,7 @@ function renderAuctionStoreReviews(storeReviews) {
|
|
|
40
40
|
const avg = storeReviews.reduce((s, r) => s + r.rating, 0) / storeReviews.length;
|
|
41
41
|
return (_jsxs(Section, { className: "mt-10", children: [_jsx(Heading, { level: 2, className: "mb-2 text-xl font-semibold text-zinc-900 dark:text-zinc-50", children: "Store Reviews" }), _jsxs(Div, { className: "mb-4 flex items-center gap-3", children: [_jsx(Span, { className: "text-3xl font-bold text-zinc-900 dark:text-zinc-50", children: avg.toFixed(1) }), _jsxs(Div, { children: [_jsx(Row, { gap: "xs", children: [1, 2, 3, 4, 5].map((star) => _jsx(Span, { className: star <= Math.round(avg) ? "text-amber-400" : "text-zinc-200 dark:text-zinc-700", children: "\u2605" }, star)) }), _jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: [storeReviews.length, " review", storeReviews.length !== 1 ? "s" : ""] })] })] }), _jsx(Stack, { gap: "sm", children: storeReviews.slice(0, 10).map((review) => (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-white dark:bg-zinc-900/60 p-4 space-y-1.5", children: [_jsx(Row, { justify: "between", align: "center", children: _jsxs(Row, { gap: "xs", align: "center", children: [_jsx(Span, { className: "text-sm font-medium text-zinc-800 dark:text-zinc-200", children: review.userName }), _jsx(Row, { gap: "xs", children: [1, 2, 3, 4, 5].map((star) => _jsx(Span, { className: `text-xs ${star <= review.rating ? "text-amber-400" : "text-zinc-200 dark:text-zinc-700"}`, children: "\u2605" }, star)) })] }) }), review.title && _jsx(Text, { className: "text-sm font-semibold text-zinc-800 dark:text-zinc-100", children: review.title }), _jsx(Text, { className: "text-sm text-zinc-600 dark:text-zinc-400 leading-relaxed", children: review.comment }), _jsx(Text, { className: "text-xs text-zinc-400 dark:text-zinc-500", children: review.productTitle })] }, review.id))) })] }));
|
|
42
42
|
}
|
|
43
|
-
export async function AuctionDetailPageView({ id, initialAuction, onPlaceBid, productFeatures }) {
|
|
43
|
+
export async function AuctionDetailPageView({ id, initialAuction, onPlaceBid, onBuyNow, productFeatures }) {
|
|
44
44
|
const product = initialAuction !== undefined
|
|
45
45
|
? (initialAuction ?? undefined)
|
|
46
46
|
: await productRepository.findByIdOrSlug(id).catch(() => undefined);
|
|
@@ -79,6 +79,7 @@ export async function AuctionDetailPageView({ id, initialAuction, onPlaceBid, pr
|
|
|
79
79
|
const isEnded = endDate ? endDate < new Date() : false;
|
|
80
80
|
const bidCount = typeof p.bidCount === "number" ? p.bidCount : 0;
|
|
81
81
|
const buyNowPrice = typeof p.buyNowPrice === "number" ? p.buyNowPrice : null;
|
|
82
|
+
const bidsHaveStarted = p.bidsHaveStarted === true;
|
|
82
83
|
const condition = typeof p.condition === "string" ? p.condition : null;
|
|
83
84
|
const featured = p.featured === true;
|
|
84
85
|
const shippingPaidBy = p.shippingPaidBy;
|
|
@@ -116,9 +117,9 @@ export async function AuctionDetailPageView({ id, initialAuction, onPlaceBid, pr
|
|
|
116
117
|
price: typeof p.price === "number" ? p.price : undefined,
|
|
117
118
|
storeId: typeof p.storeId === "string" ? p.storeId : undefined,
|
|
118
119
|
storeName: typeof p.storeName === "string" ? p.storeName : undefined,
|
|
119
|
-
} }), _jsxs(Container, { size: "xl", className: "px-4 py-6", children: [_jsxs("div", { className: "mb-4 flex items-center justify-between flex-wrap gap-2", children: [_jsxs("nav", { "aria-label": "Breadcrumb", className: "flex items-center gap-1.5 text-xs text-zinc-500 dark:text-zinc-400 flex-wrap", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.AUCTIONS), className: CLS_BREADCRUMB_LINK, children: "Auctions" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "text-zinc-700 dark:text-zinc-300 truncate max-w-[200px]", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(AuctionDetailView, { renderGallery: () => (_jsx(ProductGalleryClient, { images: images, productName: title })), renderInfo: () => renderAuctionInfoPanel({ title, currentBid, currency, bidCount, isEnded, endDate, buyNowPrice, featured, freeShipping, condition, category, categoryName, brand, brandSlug, productFeatures, features, descriptionHtml, safeSeller, storeHref }), renderBidForm: () => onPlaceBid ? (_jsx(Div, { id: "auction-bid-form", className: "space-y-3", children: _jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-5 space-y-3", children: [_jsxs(Row, { justify: "between", align: "baseline", children: [_jsx(Span, { className: "text-xl font-bold text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { className: "text-xs text-zinc-500", children: [bidCount, " ", bidCount === 1 ? "bid" : "bids", " \u00B7 min increment ", formatCurrency(minBidIncrement, currency)] })] }), _jsx(PlaceBidModalButton, { productId: String(product.id), currentBid: currentBid, startingBid: startingBid, minBidIncrement: minBidIncrement, currency: currency, isEnded: isEnded, buyNowPrice: buyNowPrice, bidCount: bidCount, tags: tags, onPlaceBid: onPlaceBid, triggerClassName: "w-full" })] }) })) : (
|
|
120
|
+
} }), _jsxs(Container, { size: "xl", className: "px-4 py-6", children: [_jsxs("div", { className: "mb-4 flex items-center justify-between flex-wrap gap-2", children: [_jsxs("nav", { "aria-label": "Breadcrumb", className: "flex items-center gap-1.5 text-xs text-zinc-500 dark:text-zinc-400 flex-wrap", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.AUCTIONS), className: CLS_BREADCRUMB_LINK, children: "Auctions" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "text-zinc-700 dark:text-zinc-300 truncate max-w-[200px]", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(AuctionDetailView, { renderGallery: () => (_jsx(ProductGalleryClient, { images: images, productName: title })), renderInfo: () => renderAuctionInfoPanel({ title, currentBid, currency, bidCount, isEnded, endDate, buyNowPrice, featured, freeShipping, condition, category, categoryName, brand, brandSlug, productFeatures, features, descriptionHtml, safeSeller, storeHref }), renderBidForm: () => onPlaceBid ? (_jsx(Div, { id: "auction-bid-form", className: "space-y-3", children: _jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-5 space-y-3", children: [_jsxs(Row, { justify: "between", align: "baseline", children: [_jsx(Span, { className: "text-xl font-bold text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { className: "text-xs text-zinc-500", children: [bidCount, " ", bidCount === 1 ? "bid" : "bids", " \u00B7 min increment ", formatCurrency(minBidIncrement, currency)] })] }), _jsx(PlaceBidModalButton, { productId: String(product.id), currentBid: currentBid, startingBid: startingBid, minBidIncrement: minBidIncrement, currency: currency, isEnded: isEnded, buyNowPrice: buyNowPrice, bidsHaveStarted: bidsHaveStarted, bidCount: bidCount, tags: tags, onPlaceBid: onPlaceBid, onBuyNow: onBuyNow, triggerClassName: "w-full" })] }) })) : (
|
|
120
121
|
/* Read-only bid panel — shown when no bid action is wired (preview/demo) */
|
|
121
|
-
_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-5 space-y-4", children: [_jsx(Div, { className: "space-y-1", children: _jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: ["Starting bid: ", formatCurrency(startingBid, currency), " \u00B7 min increment ", formatCurrency(minBidIncrement, currency)] }) }), _jsxs(Stack, { gap: "sm", children: [_jsx(Input, { type: "number", placeholder: `At least ${formatCurrency(currentBid + minBidIncrement, currency)}`, min: currentBid + minBidIncrement, "aria-label": "Your bid amount", disabled: isEnded }), _jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: isEnded, children: isEnded ? "Auction Ended" : "Place Bid" }), buyNowPrice !== null && !isEnded && (_jsxs(Button, { variant: "secondary", size: "md", className: "w-full", children: ["Buy Now \u2014 ", formatCurrency(buyNowPrice, currency)] }))] }), tags.length > 0 && (_jsx(Div, { className: "border-t border-zinc-200 dark:border-zinc-700 pt-4", children: _jsx(Row, { wrap: true, gap: "xs", children: tags.map((tag) => (_jsx(Span, { className: "rounded-full bg-zinc-100 dark:bg-zinc-800 px-2.5 py-1 text-xs text-zinc-600 dark:text-zinc-300", children: tag }, tag))) }) }))] })), renderMobileBidForm: () => !isEnded && onPlaceBid ? (_jsxs(Div, { className: "lg:hidden rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-4 space-y-3", children: [_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(Span, { className: "text-base font-bold text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { className: "text-xs text-zinc-500", children: [bidCount, " bids"] })] }), _jsx(PlaceBidModalButton, { productId: String(product.id), currentBid: currentBid, startingBid: startingBid, minBidIncrement: minBidIncrement, currency: currency, isEnded: isEnded, buyNowPrice: buyNowPrice, bidCount: bidCount, tags: tags, onPlaceBid: onPlaceBid, triggerClassName: "w-full" })] })) : !isEnded ? (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-4 lg:hidden", children: [_jsxs(Row, { align: "center", gap: "sm", className: "mb-3", children: [_jsx(Span, { className: "text-base font-bold text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { className: "text-xs text-zinc-500", children: [bidCount, " bids"] })] }), _jsx(Button, { variant: "primary", size: "md", className: "w-full", children: "Place Bid" })] })) : null, renderSublistingSection: sublistingCategoryId
|
|
122
|
+
_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-5 space-y-4", children: [_jsx(Div, { className: "space-y-1", children: _jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: ["Starting bid: ", formatCurrency(startingBid, currency), " \u00B7 min increment ", formatCurrency(minBidIncrement, currency)] }) }), _jsxs(Stack, { gap: "sm", children: [_jsx(Input, { type: "number", placeholder: `At least ${formatCurrency(currentBid + minBidIncrement, currency)}`, min: currentBid + minBidIncrement, "aria-label": "Your bid amount", disabled: isEnded }), _jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: isEnded, children: isEnded ? "Auction Ended" : "Place Bid" }), buyNowPrice !== null && !isEnded && !bidsHaveStarted && (_jsxs(Button, { variant: "secondary", size: "md", className: "w-full", disabled: true, children: ["Buy Now \u2014 ", formatCurrency(buyNowPrice, currency)] }))] }), tags.length > 0 && (_jsx(Div, { className: "border-t border-zinc-200 dark:border-zinc-700 pt-4", children: _jsx(Row, { wrap: true, gap: "xs", children: tags.map((tag) => (_jsx(Span, { className: "rounded-full bg-zinc-100 dark:bg-zinc-800 px-2.5 py-1 text-xs text-zinc-600 dark:text-zinc-300", children: tag }, tag))) }) }))] })), renderMobileBidForm: () => !isEnded && onPlaceBid ? (_jsxs(Div, { className: "lg:hidden rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-4 space-y-3", children: [_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(Span, { className: "text-base font-bold text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { className: "text-xs text-zinc-500", children: [bidCount, " bids"] })] }), _jsx(PlaceBidModalButton, { productId: String(product.id), currentBid: currentBid, startingBid: startingBid, minBidIncrement: minBidIncrement, currency: currency, isEnded: isEnded, buyNowPrice: buyNowPrice, bidsHaveStarted: bidsHaveStarted, bidCount: bidCount, tags: tags, onPlaceBid: onPlaceBid, onBuyNow: onBuyNow, triggerClassName: "w-full" })] })) : !isEnded ? (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-4 lg:hidden", children: [_jsxs(Row, { align: "center", gap: "sm", className: "mb-3", children: [_jsx(Span, { className: "text-base font-bold text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { className: "text-xs text-zinc-500", children: [bidCount, " bids"] })] }), _jsx(Button, { variant: "primary", size: "md", className: "w-full", children: "Place Bid" })] })) : null, renderSublistingSection: sublistingCategoryId
|
|
122
123
|
? () => (_jsx(SublistingCarouselSection, { sublistingCategoryId: sublistingCategoryId, currentListingId: String(product.id) }))
|
|
123
124
|
: undefined, renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-zinc-700 dark:text-zinc-300" })) : undefined, specsContent: specs.length > 0 ? (_jsx("dl", { className: "divide-y divide-zinc-100 dark:divide-zinc-800 rounded-xl border border-zinc-100 dark:border-zinc-800 overflow-hidden text-sm", children: specs.map((s, i) => (_jsxs("div", { className: "flex gap-4 px-4 py-3 bg-white dark:bg-zinc-900 even:bg-zinc-50 dark:even:bg-zinc-800/50", children: [_jsx("dt", { className: "w-36 flex-shrink-0 font-medium text-zinc-700 dark:text-zinc-300", children: s.name }), _jsxs("dd", { className: "flex-1 text-zinc-600 dark:text-zinc-400", children: [s.value, s.unit ? ` ${s.unit}` : ""] })] }, i))) })) : undefined, customTabs: customSections.map((s) => ({
|
|
124
125
|
id: s.id,
|
|
@@ -140,7 +141,7 @@ export async function AuctionDetailPageView({ id, initialAuction, onPlaceBid, pr
|
|
|
140
141
|
if (r.id === product.id || r.listingType !== "auction")
|
|
141
142
|
return false;
|
|
142
143
|
const s = r.status;
|
|
143
|
-
if (s && ["
|
|
144
|
+
if (s && ["archived", "in_review", "draft"].includes(s))
|
|
144
145
|
return false;
|
|
145
146
|
if (r.isSold === true)
|
|
146
147
|
return false;
|
|
@@ -11,11 +11,13 @@ export interface PlaceBidFormClientProps {
|
|
|
11
11
|
currency: string;
|
|
12
12
|
isEnded: boolean;
|
|
13
13
|
buyNowPrice: number | null;
|
|
14
|
+
bidsHaveStarted?: boolean;
|
|
14
15
|
bidCount: number;
|
|
15
16
|
tags?: string[];
|
|
16
17
|
onPlaceBid: (input: PlaceBidInput) => Promise<unknown>;
|
|
18
|
+
onBuyNow?: () => Promise<unknown>;
|
|
17
19
|
}
|
|
18
|
-
export declare function PlaceBidFormClient({ productId, currentBid, startingBid, minBidIncrement, currency, isEnded, buyNowPrice, bidCount, tags, onPlaceBid, }: PlaceBidFormClientProps): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
export declare function PlaceBidFormClient({ productId, currentBid, startingBid, minBidIncrement, currency, isEnded, buyNowPrice, bidsHaveStarted, bidCount, tags, onPlaceBid, onBuyNow, }: PlaceBidFormClientProps): import("react/jsx-runtime").JSX.Element;
|
|
19
21
|
/**
|
|
20
22
|
* `PlaceBidModalButton` — opens a modal that hosts the same PlaceBidFormClient.
|
|
21
23
|
*
|
|
@@ -11,13 +11,43 @@ const BID_ERROR_DISPLAY = {
|
|
|
11
11
|
BID_SELF_BID: "You cannot bid on your own listing.",
|
|
12
12
|
BID_USER_BANNED: "Your account is currently restricted from bidding. Contact support.",
|
|
13
13
|
};
|
|
14
|
-
export function PlaceBidFormClient({ productId, currentBid, startingBid, minBidIncrement, currency, isEnded, buyNowPrice, bidCount, tags = [], onPlaceBid, }) {
|
|
14
|
+
export function PlaceBidFormClient({ productId, currentBid, startingBid, minBidIncrement, currency, isEnded, buyNowPrice, bidsHaveStarted = false, bidCount, tags = [], onPlaceBid, onBuyNow, }) {
|
|
15
15
|
const minBid = currentBid + minBidIncrement;
|
|
16
16
|
const [bidAmount, setBidAmount] = useState(String(minBid));
|
|
17
17
|
const [isPending, startTransition] = useTransition();
|
|
18
|
+
const [isBuyNowPending, startBuyNowTransition] = useTransition();
|
|
18
19
|
const [error, setError] = useState(null);
|
|
19
20
|
const [success, setSuccess] = useState(false);
|
|
20
21
|
const [showLoginModal, setShowLoginModal] = useState(false);
|
|
22
|
+
const buyNowAvailable = buyNowPrice !== null && !isEnded && !bidsHaveStarted && !!onBuyNow;
|
|
23
|
+
function handleBuyNow() {
|
|
24
|
+
if (!onBuyNow)
|
|
25
|
+
return;
|
|
26
|
+
setError(null);
|
|
27
|
+
setSuccess(false);
|
|
28
|
+
startBuyNowTransition(async () => {
|
|
29
|
+
try {
|
|
30
|
+
const result = await onBuyNow();
|
|
31
|
+
if (result &&
|
|
32
|
+
typeof result === "object" &&
|
|
33
|
+
"ok" in result &&
|
|
34
|
+
result.ok === false) {
|
|
35
|
+
const r = result;
|
|
36
|
+
setError(r.error ?? "Buy Now failed. Please try again.");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
setSuccess(true);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
if (isAuthError(err)) {
|
|
43
|
+
setShowLoginModal(true);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
setError(err instanceof Error ? err.message : "Buy Now failed. Please try again.");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
21
51
|
function handleSubmit(e) {
|
|
22
52
|
e.preventDefault();
|
|
23
53
|
const amount = Number(bidAmount);
|
|
@@ -52,7 +82,7 @@ export function PlaceBidFormClient({ productId, currentBid, startingBid, minBidI
|
|
|
52
82
|
}
|
|
53
83
|
});
|
|
54
84
|
}
|
|
55
|
-
return (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-5 space-y-4", children: [_jsxs(Div, { className: "space-y-1", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-xs text-zinc-500", children: "Current bid" }), _jsx(Text, { className: "text-xs text-zinc-500", children: "Starting bid" })] }), _jsxs(Row, { justify: "between", align: "baseline", children: [_jsx(Span, { className: "text-xl font-bold text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsx(Span, { className: "text-sm text-zinc-500", children: formatCurrency(startingBid, currency) })] }), _jsxs(Text, { className: "text-xs text-zinc-400 dark:text-zinc-500", children: [bidCount, " ", bidCount === 1 ? "bid" : "bids", " \u00B7 min increment", " ", formatCurrency(minBidIncrement, currency)] })] }), _jsx(LoginRequiredModal, { isOpen: showLoginModal, onClose: () => setShowLoginModal(false), message: "You need to be signed in to place a bid. Please log in or create an account to continue." }), _jsx("form", { onSubmit: handleSubmit, children: _jsxs(Stack, { gap: "sm", children: [_jsx(Input, { type: "number", value: bidAmount, onChange: (e) => setBidAmount(e.target.value), placeholder: `At least ${formatCurrency(minBid, currency)}`, min: minBid, step: minBidIncrement, "aria-label": "Your bid amount", disabled: isEnded || isPending }), error && (_jsx(Text, { className: "text-xs text-red-600 dark:text-red-400", children: error })), success && (_jsx(Text, { className: "text-xs text-emerald-600 dark:text-emerald-400", children: "\u2713 Bid placed successfully!" })), _jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: isEnded || isPending, type: "submit", children: isPending ? "Placing Bid…" : isEnded ? "Auction Ended" : "Place Bid" }),
|
|
85
|
+
return (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-5 space-y-4", children: [_jsxs(Div, { className: "space-y-1", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-xs text-zinc-500", children: "Current bid" }), _jsx(Text, { className: "text-xs text-zinc-500", children: "Starting bid" })] }), _jsxs(Row, { justify: "between", align: "baseline", children: [_jsx(Span, { className: "text-xl font-bold text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsx(Span, { className: "text-sm text-zinc-500", children: formatCurrency(startingBid, currency) })] }), _jsxs(Text, { className: "text-xs text-zinc-400 dark:text-zinc-500", children: [bidCount, " ", bidCount === 1 ? "bid" : "bids", " \u00B7 min increment", " ", formatCurrency(minBidIncrement, currency)] })] }), _jsx(LoginRequiredModal, { isOpen: showLoginModal, onClose: () => setShowLoginModal(false), message: "You need to be signed in to place a bid. Please log in or create an account to continue." }), _jsx("form", { onSubmit: handleSubmit, children: _jsxs(Stack, { gap: "sm", children: [_jsx(Input, { type: "number", value: bidAmount, onChange: (e) => setBidAmount(e.target.value), placeholder: `At least ${formatCurrency(minBid, currency)}`, min: minBid, step: minBidIncrement, "aria-label": "Your bid amount", disabled: isEnded || isPending }), error && (_jsx(Text, { className: "text-xs text-red-600 dark:text-red-400", children: error })), success && (_jsx(Text, { className: "text-xs text-emerald-600 dark:text-emerald-400", children: "\u2713 Bid placed successfully!" })), _jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: isEnded || isPending, type: "submit", children: isPending ? "Placing Bid…" : isEnded ? "Auction Ended" : "Place Bid" }), buyNowAvailable && (_jsx(Button, { variant: "secondary", size: "md", className: "w-full", type: "button", disabled: isBuyNowPending || isPending, onClick: handleBuyNow, children: isBuyNowPending ? "Processing…" : `Buy Now — ${formatCurrency(buyNowPrice, currency)}` }))] }) }), tags.length > 0 && (_jsx(Div, { className: "border-t border-zinc-200 dark:border-zinc-700 pt-4", children: _jsx(Row, { wrap: true, gap: "xs", children: tags.map((tag) => (_jsx(Span, { className: "rounded-full bg-zinc-100 dark:bg-zinc-800 px-2.5 py-1 text-xs text-zinc-600 dark:text-zinc-300", children: tag }, tag))) }) }))] }));
|
|
56
86
|
}
|
|
57
87
|
/**
|
|
58
88
|
* `PlaceBidModalButton` — opens a modal that hosts the same PlaceBidFormClient.
|
|
@@ -14,7 +14,7 @@ export interface AuctionItem {
|
|
|
14
14
|
currentBid?: number;
|
|
15
15
|
bidCount: number;
|
|
16
16
|
auctionEndDate: string;
|
|
17
|
-
status: "published" | "draft" | "
|
|
17
|
+
status: "published" | "draft" | "in_review" | "archived";
|
|
18
18
|
sellerId: string;
|
|
19
19
|
storeId?: string;
|
|
20
20
|
storeSlug?: string;
|
|
@@ -147,5 +147,5 @@ export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarS
|
|
|
147
147
|
opacity: darkBackground.overlay?.opacity ?? 0,
|
|
148
148
|
},
|
|
149
149
|
};
|
|
150
|
-
return (_jsx(QueryClientProvider, { client: queryClient, children: _jsxs(Div, { className: "flex min-h-screen w-full flex-col overflow-x-clip transition-colors duration-300", children: [_jsx(BackgroundRenderer, { mode: theme === "dark" ? "dark" : "light", lightMode: normalizedLightBackground, darkMode: normalizedDarkBackground }), _jsxs(Div, { ref: headerRef, className: "sticky top-0 z-50 w-full", children: [_jsx(TitleBar, { onToggleSidebar: handleTogglePublicSidebar, sidebarOpen: sidebarOpen, onSearchToggle: () => setSearchOpen((prev) => !prev), searchOpen: searchOpen, brandName: brandName, brandShortName: brandShortName, siteLogoUrl: siteLogoUrl, logoHref: logoHref, promotionsHref: promotionsHref, cartHref: cartHref, wishlistHref: wishlistHref, userId: userId, profileHref: profileHref, loginHref: loginHref, registerHref: registerHref, user: user, navSlot: titleBarNavSlot, notificationSlot: titleBarNotificationSlot, devSlot: titleBarDevSlot, promoStripText: titleBarPromoStripText, isDark: theme === "dark", onToggleTheme: showThemeToggle ? toggleTheme : undefined, onBeforeToggleDashboardNav: handleBeforeDashboardNavToggle, suppressDashboardNav: suppressDashboardNav, hideSidebarToggle: hideSidebarToggle }), _jsx(NavbarWithSettings, { navItems: navItems, hiddenNavItems: hiddenNavItems, permissions: authUser?.permissions }), searchOpen && (searchSlotRenderer ? searchSlotRenderer(() => setSearchOpen(false)) : searchSlot)] }), eventBannerSlot, _jsx(AutoBreadcrumbs, {}), _jsxs(Div, { className: "relative flex w-full flex-1 overflow-x-clip", children: [_jsx(SidebarLayout, { isOpen: sidebarOpen, ariaLabel: "Secondary navigation", header: user ? (_jsx(SidebarUserHeader, { user: user, onClose: () => setSidebarOpen(false) })) : (_jsx(SidebarGuestHeader, { sidebarTitle: sidebarTitle, onClose: () => setSidebarOpen(false) })), onClose: () => setSidebarOpen(false), children: sidebarContent }), _jsx(Main, { id: "main-content", className: `w-full flex-1 ${hasBottomActions ? "mb-28" : "mb-16"} md:mb-0`, children: _jsx(Div, { className: contentClassName ?? "w-full px-5 py-6 md:px-6 lg:px-8"
|
|
150
|
+
return (_jsx(QueryClientProvider, { client: queryClient, children: _jsxs(Div, { className: "flex min-h-screen w-full flex-col overflow-x-clip transition-colors duration-300", children: [_jsx(BackgroundRenderer, { mode: theme === "dark" ? "dark" : "light", lightMode: normalizedLightBackground, darkMode: normalizedDarkBackground }), _jsxs(Div, { ref: headerRef, className: "sticky top-0 z-50 w-full", children: [_jsx(TitleBar, { onToggleSidebar: handleTogglePublicSidebar, sidebarOpen: sidebarOpen, onSearchToggle: () => setSearchOpen((prev) => !prev), searchOpen: searchOpen, brandName: brandName, brandShortName: brandShortName, siteLogoUrl: siteLogoUrl, logoHref: logoHref, promotionsHref: promotionsHref, cartHref: cartHref, wishlistHref: wishlistHref, userId: userId, profileHref: profileHref, loginHref: loginHref, registerHref: registerHref, user: user, navSlot: titleBarNavSlot, notificationSlot: titleBarNotificationSlot, devSlot: titleBarDevSlot, promoStripText: titleBarPromoStripText, isDark: theme === "dark", onToggleTheme: showThemeToggle ? toggleTheme : undefined, onBeforeToggleDashboardNav: handleBeforeDashboardNavToggle, suppressDashboardNav: suppressDashboardNav, hideSidebarToggle: hideSidebarToggle }), _jsx(NavbarWithSettings, { navItems: navItems, hiddenNavItems: hiddenNavItems, permissions: authUser?.permissions }), searchOpen && (searchSlotRenderer ? searchSlotRenderer(() => setSearchOpen(false)) : searchSlot)] }), eventBannerSlot, _jsx(AutoBreadcrumbs, {}), _jsxs(Div, { className: "relative flex w-full flex-1 overflow-x-clip", children: [_jsx(SidebarLayout, { isOpen: sidebarOpen, ariaLabel: "Secondary navigation", header: user ? (_jsx(SidebarUserHeader, { user: user, onClose: () => setSidebarOpen(false) })) : (_jsx(SidebarGuestHeader, { sidebarTitle: sidebarTitle, onClose: () => setSidebarOpen(false) })), onClose: () => setSidebarOpen(false), children: sidebarContent }), _jsx(Main, { id: "main-content", className: `w-full flex-1 flex flex-col ${hasBottomActions ? "mb-28" : "mb-16"} md:mb-0`, children: _jsx(Div, { className: `flex-1 ${contentClassName ?? "w-full px-5 py-6 md:px-6 lg:px-8"}`, children: children }) })] }), _jsx(BackToTop, {}), _jsx(FooterLayout, { ...footer }), _jsx(BottomActions, {}), _jsx(BottomNavbar, { user: user, homeHref: homeHref, shopHref: shopHref, cartHref: cartHref, profileHref: profileHref, loginHref: loginHref, onSearchToggle: () => setSearchOpen((prev) => !prev), navItems: navItems, onMoreToggle: hasDashboardNav ? toggleDashboardNav : handleTogglePublicSidebar }), _jsx(UnsavedChangesModal, {})] }) }));
|
|
151
151
|
}
|
|
@@ -19,17 +19,8 @@ import { useCategoryTree, categoriesToFacetOptions } from "../../categories/hook
|
|
|
19
19
|
import { useBrands } from "../../products/hooks/useBrands";
|
|
20
20
|
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
21
21
|
import { TABLE_KEYS, VIEW_MODE } from "../../../constants/table-keys";
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
const DEFAULT_SORT = sortBy(PRODUCT_FIELDS.CREATED_AT);
|
|
25
|
-
const PREORDER_SORT_OPTIONS = [
|
|
26
|
-
{ value: sortBy(PRODUCT_FIELDS.CREATED_AT), label: "Newest First" },
|
|
27
|
-
{ value: sortBy(PRODUCT_FIELDS.CREATED_AT, "ASC"), label: "Oldest First" },
|
|
28
|
-
{ value: sortBy(PRODUCT_FIELDS.PRE_ORDER_DELIVERY_DATE, "ASC"), label: "Delivery: Soonest" },
|
|
29
|
-
{ value: sortBy(PRODUCT_FIELDS.PRE_ORDER_DELIVERY_DATE), label: "Delivery: Furthest" },
|
|
30
|
-
{ value: sortBy(PRODUCT_FIELDS.PRICE, "ASC"), label: "Price: Low to High" },
|
|
31
|
-
{ value: sortBy(PRODUCT_FIELDS.PRICE), label: "Price: High to Low" },
|
|
32
|
-
];
|
|
22
|
+
import { PREORDER_SORT_OPTIONS } from "../../products/constants/sieve";
|
|
23
|
+
const DEFAULT_SORT = PREORDER_SORT_OPTIONS[2].value;
|
|
33
24
|
const FILTER_KEYS = [TABLE_KEYS.CATEGORY, TABLE_KEYS.BRAND, TABLE_KEYS.MIN_PRICE, TABLE_KEYS.MAX_PRICE, TABLE_KEYS.STORE_ID, TABLE_KEYS.PREORDER_STATUS, TABLE_KEYS.DATE_FROM, TABLE_KEYS.DATE_TO];
|
|
34
25
|
export function PreOrdersIndexListing({ initialData, categorySlug, brandName }) {
|
|
35
26
|
const table = useUrlTable({ defaults: { pageSize: "24", sort: DEFAULT_SORT } });
|
|
@@ -21,9 +21,7 @@ import { sanitizeProductForPublic } from "../../utils/sanitize";
|
|
|
21
21
|
const ERR_DB_NOT_REGISTERED = "Database provider not registered";
|
|
22
22
|
const ERR_PRODUCT_NOT_FOUND = "Product not found";
|
|
23
23
|
const UNAVAILABLE_PRODUCT_STATUSES = new Set([
|
|
24
|
-
"
|
|
25
|
-
"out_of_stock",
|
|
26
|
-
"discontinued",
|
|
24
|
+
"archived",
|
|
27
25
|
]);
|
|
28
26
|
function getRepo() {
|
|
29
27
|
const { db } = getProviders();
|
|
@@ -43,10 +41,8 @@ const productUpdateSchema = z
|
|
|
43
41
|
.enum([
|
|
44
42
|
"draft",
|
|
45
43
|
"published",
|
|
44
|
+
"in_review",
|
|
46
45
|
"archived",
|
|
47
|
-
"sold",
|
|
48
|
-
"discontinued",
|
|
49
|
-
"out_of_stock",
|
|
50
46
|
])
|
|
51
47
|
.optional(),
|
|
52
48
|
mainImage: z.string().optional(),
|
|
@@ -145,7 +141,7 @@ export const PATCH = createRouteHandler({
|
|
|
145
141
|
},
|
|
146
142
|
});
|
|
147
143
|
// --- DELETE /api/products/[id] ------------------------------------------------
|
|
148
|
-
// Soft-delete (sets status to "
|
|
144
|
+
// Soft-delete (sets status to "archived"). Auth required.
|
|
149
145
|
export const DELETE = createRouteHandler({
|
|
150
146
|
auth: true,
|
|
151
147
|
handler: async ({ user, params }) => {
|
|
@@ -166,7 +162,7 @@ export const DELETE = createRouteHandler({
|
|
|
166
162
|
return NextResponse.json({ success: false, error: "Not authorized to delete this product" }, { status: 403 });
|
|
167
163
|
}
|
|
168
164
|
await repo.update(id, {
|
|
169
|
-
status: "
|
|
165
|
+
status: "archived",
|
|
170
166
|
updatedAt: new Date().toISOString(),
|
|
171
167
|
});
|
|
172
168
|
// SB-UNI-V — bundle stock-sync handled by onProductStockChange Function.
|
|
@@ -13,19 +13,11 @@ import { pushWishlistOp } from "../../cart/utils/pending-ops";
|
|
|
13
13
|
import { useCategoryTree, categoriesToFacetOptions } from "../../categories/hooks/useCategoryTree";
|
|
14
14
|
import { useBrands } from "../hooks/useBrands";
|
|
15
15
|
import { TABLE_KEYS, VIEW_MODE } from "../../../constants/table-keys";
|
|
16
|
-
import { sortBy } from "../../../constants/sort";
|
|
17
|
-
import { PRODUCT_FIELDS } from "../../../constants/field-names";
|
|
18
16
|
import { useAuthGate } from "../../../react/hooks/useAuthGate";
|
|
19
17
|
import { ACTION_ID } from "../constants/action-defs";
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
{ value: sortBy(PRODUCT_FIELDS.AUCTION_END_DATE), label: "Ending Latest" },
|
|
24
|
-
{ value: sortBy(PRODUCT_FIELDS.CURRENT_BID, "ASC"), label: "Bid: Low to High" },
|
|
25
|
-
{ value: sortBy(PRODUCT_FIELDS.CURRENT_BID), label: "Bid: High to Low" },
|
|
26
|
-
{ value: sortBy(PRODUCT_FIELDS.CREATED_AT), label: "Newly Listed" },
|
|
27
|
-
{ value: sortBy(PRODUCT_FIELDS.CREATED_AT, "ASC"), label: "Oldest Listed" },
|
|
28
|
-
];
|
|
18
|
+
import { AUCTION_PUBLIC_SORT_OPTIONS } from "../constants/sieve";
|
|
19
|
+
const DEFAULT_SORT = AUCTION_PUBLIC_SORT_OPTIONS[0].value;
|
|
20
|
+
const AUCTION_SORT_OPTIONS = AUCTION_PUBLIC_SORT_OPTIONS;
|
|
29
21
|
const FILTER_KEYS = [TABLE_KEYS.CATEGORY, TABLE_KEYS.BRAND, TABLE_KEYS.MIN_BID, TABLE_KEYS.MAX_BID, TABLE_KEYS.STORE_ID, TABLE_KEYS.DATE_FROM, TABLE_KEYS.DATE_TO];
|
|
30
22
|
export function AuctionsIndexListing({ initialData, categorySlug, brandName }) {
|
|
31
23
|
const table = useUrlTable({ defaults: { pageSize: "24", sort: DEFAULT_SORT } });
|
|
@@ -10,17 +10,9 @@ import { useBrands } from "../hooks/useBrands";
|
|
|
10
10
|
import { MarketplacePrizeDrawCard } from "./MarketplacePrizeDrawCard";
|
|
11
11
|
import { ProductFilters } from "./ProductFilters";
|
|
12
12
|
import { TABLE_KEYS, VIEW_MODE } from "../../../constants/table-keys";
|
|
13
|
-
import {
|
|
13
|
+
import { PRIZE_DRAW_SORT_OPTIONS } from "../constants/sieve";
|
|
14
14
|
import { PRODUCT_FIELDS } from "../../../constants/field-names";
|
|
15
|
-
const DEFAULT_SORT =
|
|
16
|
-
const PRIZE_DRAW_SORT_OPTIONS = [
|
|
17
|
-
{ value: sortBy(PRODUCT_FIELDS.CREATED_AT), label: "Newest First" },
|
|
18
|
-
{ value: sortBy(PRODUCT_FIELDS.CREATED_AT, "ASC"), label: "Oldest First" },
|
|
19
|
-
{ value: sortBy(PRODUCT_FIELDS.PRIZE_REVEAL_WINDOW_START, "ASC"), label: "Reveal: Soonest" },
|
|
20
|
-
{ value: sortBy(PRODUCT_FIELDS.PRIZE_REVEAL_WINDOW_START), label: "Reveal: Furthest" },
|
|
21
|
-
{ value: sortBy(PRODUCT_FIELDS.PRICE, "ASC"), label: "Entry: Low to High" },
|
|
22
|
-
{ value: sortBy(PRODUCT_FIELDS.PRICE), label: "Entry: High to Low" },
|
|
23
|
-
];
|
|
15
|
+
const DEFAULT_SORT = PRIZE_DRAW_SORT_OPTIONS[0].value;
|
|
24
16
|
const FILTER_KEYS = [
|
|
25
17
|
TABLE_KEYS.CATEGORY,
|
|
26
18
|
TABLE_KEYS.BRAND,
|
|
@@ -15,18 +15,27 @@ export declare const PRODUCT_ADMIN_SORT_OPTIONS: readonly [{
|
|
|
15
15
|
}, {
|
|
16
16
|
readonly value: string;
|
|
17
17
|
readonly label: "Oldest First";
|
|
18
|
+
}, {
|
|
19
|
+
readonly value: string;
|
|
20
|
+
readonly label: "Price: Low to High";
|
|
18
21
|
}, {
|
|
19
22
|
readonly value: string;
|
|
20
23
|
readonly label: "Price: High to Low";
|
|
21
24
|
}, {
|
|
22
25
|
readonly value: string;
|
|
23
|
-
readonly label: "
|
|
26
|
+
readonly label: "Name: A–Z";
|
|
24
27
|
}, {
|
|
25
28
|
readonly value: string;
|
|
26
|
-
readonly label: "
|
|
29
|
+
readonly label: "Name: Z–A";
|
|
27
30
|
}, {
|
|
28
31
|
readonly value: string;
|
|
29
|
-
readonly label: "
|
|
32
|
+
readonly label: "Featured First";
|
|
33
|
+
}, {
|
|
34
|
+
readonly value: string;
|
|
35
|
+
readonly label: "Promoted First";
|
|
36
|
+
}, {
|
|
37
|
+
readonly value: string;
|
|
38
|
+
readonly label: "Recently Updated";
|
|
30
39
|
}, {
|
|
31
40
|
readonly value: string;
|
|
32
41
|
readonly label: "Most Viewed";
|
|
@@ -37,15 +46,30 @@ export declare const PRODUCT_SELLER_SORT_OPTIONS: readonly [{
|
|
|
37
46
|
}, {
|
|
38
47
|
readonly value: string;
|
|
39
48
|
readonly label: "Oldest First";
|
|
49
|
+
}, {
|
|
50
|
+
readonly value: string;
|
|
51
|
+
readonly label: "Price: Low to High";
|
|
40
52
|
}, {
|
|
41
53
|
readonly value: string;
|
|
42
54
|
readonly label: "Price: High to Low";
|
|
43
55
|
}, {
|
|
44
56
|
readonly value: string;
|
|
45
|
-
readonly label: "
|
|
57
|
+
readonly label: "Name: A–Z";
|
|
46
58
|
}, {
|
|
47
59
|
readonly value: string;
|
|
48
|
-
readonly label: "
|
|
60
|
+
readonly label: "Name: Z–A";
|
|
61
|
+
}, {
|
|
62
|
+
readonly value: string;
|
|
63
|
+
readonly label: "Featured First";
|
|
64
|
+
}, {
|
|
65
|
+
readonly value: string;
|
|
66
|
+
readonly label: "Promoted First";
|
|
67
|
+
}, {
|
|
68
|
+
readonly value: string;
|
|
69
|
+
readonly label: "Recently Updated";
|
|
70
|
+
}, {
|
|
71
|
+
readonly value: string;
|
|
72
|
+
readonly label: "Most Viewed";
|
|
49
73
|
}];
|
|
50
74
|
export declare const PRODUCT_PUBLIC_SORT_OPTIONS: readonly [{
|
|
51
75
|
readonly value: string;
|
|
@@ -55,16 +79,16 @@ export declare const PRODUCT_PUBLIC_SORT_OPTIONS: readonly [{
|
|
|
55
79
|
readonly label: "Oldest First";
|
|
56
80
|
}, {
|
|
57
81
|
readonly value: string;
|
|
58
|
-
readonly label: "Price:
|
|
82
|
+
readonly label: "Price: Low to High";
|
|
59
83
|
}, {
|
|
60
84
|
readonly value: string;
|
|
61
|
-
readonly label: "Price:
|
|
85
|
+
readonly label: "Price: High to Low";
|
|
62
86
|
}, {
|
|
63
87
|
readonly value: string;
|
|
64
|
-
readonly label: "
|
|
88
|
+
readonly label: "Name: A–Z";
|
|
65
89
|
}, {
|
|
66
90
|
readonly value: string;
|
|
67
|
-
readonly label: "
|
|
91
|
+
readonly label: "Name: Z–A";
|
|
68
92
|
}, {
|
|
69
93
|
readonly value: string;
|
|
70
94
|
readonly label: "Most Viewed";
|