@mohasinac/appkit 2.7.17 → 2.7.19
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/features/bundles/actions.d.ts +15 -0
- package/dist/_internal/server/features/bundles/actions.js +48 -0
- package/dist/_internal/server/features/bundles/index.d.ts +1 -0
- package/dist/_internal/server/features/bundles/index.js +1 -0
- package/dist/_internal/server/features/checkout/actions.d.ts +4 -0
- package/dist/_internal/server/features/checkout/actions.js +23 -12
- package/dist/_internal/server/features/payments/data.d.ts +5 -3
- package/dist/_internal/server/features/payments/data.js +13 -8
- package/dist/_internal/server/features/payments/index.d.ts +1 -1
- package/dist/_internal/server/features/payments/index.js +1 -1
- package/dist/_internal/server/features/seo/manifest.js +9 -3
- package/dist/_internal/shared/features/categories/bundle-config.d.ts +24 -0
- package/dist/_internal/shared/features/categories/bundle-config.js +28 -0
- package/dist/_internal/shared/features/categories/bundle-copy.d.ts +7 -0
- package/dist/_internal/shared/features/categories/bundle-copy.js +9 -0
- package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +69 -19
- package/dist/_internal/shared/features/categories/bundle-schemas.js +20 -2
- package/dist/_internal/shared/features/checkout/config.d.ts +1 -1
- package/dist/_internal/shared/features/checkout/config.js +1 -1
- package/dist/_internal/shared/features/payments/config.d.ts +2 -1
- package/dist/_internal/shared/features/payments/config.js +2 -1
- package/dist/_internal/shared/features/products/schema.d.ts +8 -8
- package/dist/constants/field-names.d.ts +8 -1
- package/dist/constants/field-names.js +9 -2
- package/dist/constants/table-keys.d.ts +3 -0
- package/dist/constants/table-keys.js +3 -0
- package/dist/features/admin/components/AdminSiteSettingsView.js +17 -15
- package/dist/features/admin/components/DashboardStats.js +20 -12
- package/dist/features/admin/components/analytics/AdminAnalyticsCharts.js +18 -2
- package/dist/features/admin/components/analytics/AdminStatCard.d.ts +7 -1
- package/dist/features/admin/components/analytics/AdminStatCard.js +13 -2
- package/dist/features/admin/schemas/firestore.d.ts +39 -4
- package/dist/features/admin/schemas/firestore.js +14 -1
- package/dist/features/categories/components/BundleBuyNowCta.d.ts +3 -1
- package/dist/features/categories/components/BundleBuyNowCta.js +3 -3
- package/dist/features/categories/components/BundleDynamicRuleEditor.d.ts +1 -1
- package/dist/features/categories/components/BundleDynamicRuleEditor.js +2 -1
- package/dist/features/categories/components/BundlesListView.d.ts +4 -1
- package/dist/features/categories/components/BundlesListView.js +2 -2
- package/dist/features/categories/components/CategoryBundlesListing.d.ts +4 -1
- package/dist/features/categories/components/CategoryBundlesListing.js +9 -8
- package/dist/features/categories/schemas/firestore.d.ts +29 -1
- package/dist/features/checkout/actions/checkout-actions.d.ts +11 -0
- package/dist/features/checkout/actions/checkout-actions.js +26 -0
- package/dist/features/filters/FilterFacetSection.d.ts +6 -1
- package/dist/features/filters/FilterFacetSection.js +25 -2
- package/dist/features/homepage/components/FeaturedBundlesSection.d.ts +4 -1
- package/dist/features/homepage/components/FeaturedBundlesSection.js +9 -8
- package/dist/features/orders/actions/refund-actions.js +10 -7
- package/dist/features/orders/schemas/firestore.d.ts +16 -13
- package/dist/features/orders/schemas/firestore.js +6 -5
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +1 -1
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +2 -1
- package/dist/features/products/components/AuctionsIndexListing.js +2 -1
- package/dist/features/products/components/ProductDetailPageView.js +3 -1
- package/dist/features/products/components/ProductDetailView.d.ts +3 -1
- package/dist/features/products/components/ProductDetailView.js +2 -2
- package/dist/features/products/components/ProductFilters.d.ts +10 -4
- package/dist/features/products/components/ProductFilters.js +20 -16
- package/dist/features/products/components/ProductGrid.js +2 -2
- package/dist/features/products/components/ProductsIndexListing.js +2 -1
- package/dist/features/products/constants/action-defs.d.ts +64 -22
- package/dist/features/products/constants/action-defs.js +101 -64
- package/dist/features/products/constants/product-features.constants.js +5 -1
- package/dist/features/products/schemas/index.d.ts +2 -2
- package/dist/features/products/schemas/product-features.d.ts +1 -1
- package/dist/features/products/schemas/product-features.validators.d.ts +10 -10
- package/dist/features/products/schemas/product-features.validators.js +4 -0
- package/dist/features/seller/components/SellerCouponEditorView.js +2 -2
- package/dist/features/seller/components/analytics/SellerAnalyticsStats.js +10 -4
- package/dist/features/stores/components/StoreBundlesPageView.d.ts +4 -1
- package/dist/features/stores/components/StoreBundlesPageView.js +2 -2
- package/dist/features/stores/components/StoresIndexListing.js +2 -1
- package/dist/providers/db-firebase/admin-app-lite.js +8 -2
- package/dist/providers/db-firebase/admin.js +8 -2
- package/dist/seed/addresses-seed-data.js +24 -7
- package/dist/seed/cart-seed-data.d.ts +10 -10
- package/dist/seed/cart-seed-data.js +39 -2
- package/dist/seed/grouped-listings-seed-data.js +47 -0
- package/dist/seed/orders-seed-data.js +150 -0
- package/dist/seed/products-standard-seed-data.js +42 -0
- package/dist/seed/site-settings-seed-data.js +14 -4
- package/dist/seed/users-seed-data.js +1 -1
- package/dist/seed/wishlists-seed-data.js +7 -0
- package/dist/server-entry.d.ts +2 -1
- package/dist/server-entry.js +2 -1
- package/dist/server.d.ts +2 -1
- package/dist/server.js +3 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/SideDrawer.style.css +49 -11
- package/package.json +1 -1
|
@@ -7,12 +7,12 @@ export type ShippingMethod = "custom" | "shiprocket";
|
|
|
7
7
|
export type RefundType = "full" | "partial";
|
|
8
8
|
export type OrderPayoutStatus = "eligible" | "requested" | "paid";
|
|
9
9
|
export type RefundStatus = "pending" | "processing" | "completed" | "rejected";
|
|
10
|
-
/** Runtime-accessible shipping method values
|
|
10
|
+
/** Runtime-accessible shipping method values â€" use instead of bare string literals. */
|
|
11
11
|
export declare const ShippingMethodValues: {
|
|
12
12
|
readonly CUSTOM: "custom";
|
|
13
13
|
readonly SHIPROCKET: "shiprocket";
|
|
14
14
|
};
|
|
15
|
-
/** Runtime-accessible order status values
|
|
15
|
+
/** Runtime-accessible order status values â€" use instead of bare string literals. */
|
|
16
16
|
export declare const OrderStatusValues: {
|
|
17
17
|
readonly PENDING: "pending";
|
|
18
18
|
readonly CONFIRMED: "confirmed";
|
|
@@ -24,7 +24,7 @@ export declare const OrderStatusValues: {
|
|
|
24
24
|
readonly RETURN_REQUESTED: "return_requested";
|
|
25
25
|
readonly RETURNED: "returned";
|
|
26
26
|
};
|
|
27
|
-
/** Runtime-accessible payment status values
|
|
27
|
+
/** Runtime-accessible payment status values â€" use instead of bare string literals. */
|
|
28
28
|
export declare const PaymentStatusValues: {
|
|
29
29
|
readonly PENDING: "pending";
|
|
30
30
|
readonly PROCESSING: "processing";
|
|
@@ -33,34 +33,35 @@ export declare const PaymentStatusValues: {
|
|
|
33
33
|
readonly REFUNDED: "refunded";
|
|
34
34
|
readonly PARTIAL_REFUND: "partial_refund";
|
|
35
35
|
};
|
|
36
|
-
/** Runtime-accessible payment method values
|
|
36
|
+
/** Runtime-accessible payment method values â€" use instead of bare string literals. */
|
|
37
37
|
export declare const PaymentMethodValues: {
|
|
38
38
|
readonly COD: "cod";
|
|
39
39
|
readonly ONLINE: "online";
|
|
40
40
|
readonly UPI_MANUAL: "upi_manual";
|
|
41
41
|
readonly RAZORPAY: "razorpay";
|
|
42
|
+
readonly ADMIN_BYPASS: "admin_bypass";
|
|
42
43
|
};
|
|
43
|
-
/** Runtime-accessible refund status values
|
|
44
|
+
/** Runtime-accessible refund status values â€" use instead of bare string literals. */
|
|
44
45
|
export declare const RefundStatusValues: {
|
|
45
46
|
readonly PENDING: "pending";
|
|
46
47
|
readonly PROCESSING: "processing";
|
|
47
48
|
readonly COMPLETED: "completed";
|
|
48
49
|
readonly REJECTED: "rejected";
|
|
49
50
|
};
|
|
50
|
-
/** Firestore storage shape for an order line item
|
|
51
|
+
/** Firestore storage shape for an order line item â€" distinct from the API display model OrderItem */
|
|
51
52
|
export interface OrderDocumentItem {
|
|
52
53
|
productId: string;
|
|
53
54
|
productTitle: string;
|
|
54
55
|
quantity: number;
|
|
55
56
|
unitPrice: number;
|
|
56
57
|
totalPrice: number;
|
|
57
|
-
/** SB8-F
|
|
58
|
+
/** SB8-F â€" set when the item is a prize-draw entry; drives the reveals badge. */
|
|
58
59
|
listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live";
|
|
59
|
-
/** SB8-F
|
|
60
|
+
/** SB8-F â€" per-item reveal status; flips through pending â†' open â†' revealed/closed. */
|
|
60
61
|
prizeRevealStatus?: "pending" | "open" | "closed" | "revealed";
|
|
61
|
-
/** SB8-F
|
|
62
|
+
/** SB8-F â€" ISO timestamp; deadline by which the buyer must claim the prize. */
|
|
62
63
|
prizeRevealDeadline?: string;
|
|
63
|
-
/** SB8-F
|
|
64
|
+
/** SB8-F â€" set after the reveal API picks a winner. */
|
|
64
65
|
revealedItemNumber?: number;
|
|
65
66
|
}
|
|
66
67
|
/**
|
|
@@ -163,13 +164,13 @@ export interface OrderDocument {
|
|
|
163
164
|
};
|
|
164
165
|
/** Deadline for the buyer to claim the won prize (typically 7 days). */
|
|
165
166
|
prizeRevealDeadline?: Date;
|
|
166
|
-
/** True once `prizeRevealDeadline` passes without a claim
|
|
167
|
+
/** True once `prizeRevealDeadline` passes without a claim â€" auto-forfeit. */
|
|
167
168
|
prizeRevealExpired?: boolean;
|
|
168
169
|
/** Source product id when the order came from a prize-draw entry. */
|
|
169
170
|
prizeDrawProductId?: string;
|
|
170
171
|
/** True for prize-draw entries and bundle purchases that bypass refund. */
|
|
171
172
|
isNonRefundable?: boolean;
|
|
172
|
-
/** Set when the order came from a bundle
|
|
173
|
+
/** Set when the order came from a bundle â€" points back to `bundles/{id}`. */
|
|
173
174
|
bundleId?: string;
|
|
174
175
|
/**
|
|
175
176
|
* Internal reference linking sibling orders created from the same checkout
|
|
@@ -184,11 +185,13 @@ export interface OrderDocument {
|
|
|
184
185
|
refunds?: OrderRefundEvent[];
|
|
185
186
|
/**
|
|
186
187
|
* False once ANY refund (full or partial) has been posted. Once false it
|
|
187
|
-
* stays false — no dispute, RMA request, or
|
|
188
|
+
* stays false — no dispute, RMA request, or "Item Not Received" claim may
|
|
188
189
|
* be filed. Both buyer and seller must acknowledge this in the refund UI
|
|
189
190
|
* via `confirmIrrevocable: true` before the refund is posted.
|
|
190
191
|
*/
|
|
191
192
|
contestable?: boolean;
|
|
193
|
+
/** UID of the admin who triggered an admin-bypass checkout. Set only when paymentMethod === "admin_bypass". */
|
|
194
|
+
adminBypassBy?: string;
|
|
192
195
|
/** Signed-URL media slug for a shipping-proof document (label, AWB scan). */
|
|
193
196
|
shippingProofUrl?: string;
|
|
194
197
|
shippingProofMimeType?: string;
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* Orders Firestore Document Types & Constants
|
|
3
3
|
*/
|
|
4
4
|
import { generateOrderId } from "../../../utils/id-generators";
|
|
5
|
-
/** Runtime-accessible shipping method values
|
|
5
|
+
/** Runtime-accessible shipping method values â€" use instead of bare string literals. */
|
|
6
6
|
export const ShippingMethodValues = {
|
|
7
7
|
CUSTOM: "custom",
|
|
8
8
|
SHIPROCKET: "shiprocket",
|
|
9
9
|
};
|
|
10
|
-
/** Runtime-accessible order status values
|
|
10
|
+
/** Runtime-accessible order status values â€" use instead of bare string literals. */
|
|
11
11
|
export const OrderStatusValues = {
|
|
12
12
|
PENDING: "pending",
|
|
13
13
|
CONFIRMED: "confirmed",
|
|
@@ -19,7 +19,7 @@ export const OrderStatusValues = {
|
|
|
19
19
|
RETURN_REQUESTED: "return_requested",
|
|
20
20
|
RETURNED: "returned",
|
|
21
21
|
};
|
|
22
|
-
/** Runtime-accessible payment status values
|
|
22
|
+
/** Runtime-accessible payment status values â€" use instead of bare string literals. */
|
|
23
23
|
export const PaymentStatusValues = {
|
|
24
24
|
PENDING: "pending",
|
|
25
25
|
PROCESSING: "processing",
|
|
@@ -28,14 +28,15 @@ export const PaymentStatusValues = {
|
|
|
28
28
|
REFUNDED: "refunded",
|
|
29
29
|
PARTIAL_REFUND: "partial_refund",
|
|
30
30
|
};
|
|
31
|
-
/** Runtime-accessible payment method values
|
|
31
|
+
/** Runtime-accessible payment method values â€" use instead of bare string literals. */
|
|
32
32
|
export const PaymentMethodValues = {
|
|
33
33
|
COD: "cod",
|
|
34
34
|
ONLINE: "online",
|
|
35
35
|
UPI_MANUAL: "upi_manual",
|
|
36
36
|
RAZORPAY: "razorpay",
|
|
37
|
+
ADMIN_BYPASS: "admin_bypass",
|
|
37
38
|
};
|
|
38
|
-
/** Runtime-accessible refund status values
|
|
39
|
+
/** Runtime-accessible refund status values â€" use instead of bare string literals. */
|
|
39
40
|
export const RefundStatusValues = {
|
|
40
41
|
PENDING: "pending",
|
|
41
42
|
PROCESSING: "processing",
|
|
@@ -51,7 +51,7 @@ export function MarketplacePreorderCard({ product, className = "", variant = "gr
|
|
|
51
51
|
// The hook rolls back optimistic state when the mutation fails.
|
|
52
52
|
}
|
|
53
53
|
}, [toggle]);
|
|
54
|
-
return (_jsxs(BaseListingCard, { isSelected: isSelected, variant: variant, className: className, onMouseDown: !isSelected ? longPress.onMouseDown : undefined, onMouseUp: !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: !isSelected ? longPress.onTouchEnd : undefined, children: [_jsxs(BaseListingCard.Hero, { aspect: "square", variant: variant, children: [_jsx(TextLink, { href: detailHref, className: "absolute inset-0 block", children: _jsx(MediaImage, { src: product.mainImage, alt: product.title, size: "card", fallback: "Product image" }) }), _jsxs(Div, { className: "absolute right-2 top-2 flex flex-col items-end gap-1", children: [_jsx(Span, { className: "inline-flex items-center rounded-full bg-cobalt px-2 py-0.5 text-xs font-medium text-white", children: mergedLabels.preOrderBadge }), product.featured && (_jsx(Span, { className: "inline-flex items-center rounded-full bg-amber-500 px-2 py-0.5 text-xs font-medium text-white", children: mergedLabels.featuredBadge }))] }), onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (event) => {
|
|
54
|
+
return (_jsxs(BaseListingCard, { isSelected: isSelected, variant: variant, className: className, onMouseDown: !isSelected ? longPress.onMouseDown : undefined, onMouseUp: !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: !isSelected ? longPress.onTouchEnd : undefined, children: [_jsxs(BaseListingCard.Hero, { aspect: "square", variant: variant, children: [_jsx(TextLink, { href: detailHref, className: "absolute inset-0 block", children: _jsx(MediaImage, { src: product.mainImage, alt: product.title, size: "card", fallback: "Product image" }) }), _jsxs(Div, { className: "absolute right-2 top-2 flex flex-col items-end gap-1", children: [_jsx(Span, { className: "inline-flex items-center rounded-full bg-cobalt px-2 py-0.5 text-xs font-medium text-white", children: mergedLabels.preOrderBadge }), product.featured && (_jsx(Span, { className: "inline-flex items-center rounded-full bg-amber-500 px-2 py-0.5 text-xs font-medium text-white", children: mergedLabels.featuredBadge }))] }), _jsxs(Div, { className: "absolute left-2 top-2 flex flex-col gap-1 pointer-events-none", children: [product.partOfBundleIds && product.partOfBundleIds.length > 0 && (_jsx(Span, { className: "rounded-full bg-teal-600 px-2 py-0.5 text-xs font-bold text-white", title: product.partOfBundleTitles?.[0] ? `In bundle: ${product.partOfBundleTitles[0]}` : "In a bundle", children: "Bundled" })), product.groupId && (_jsx(Span, { className: "rounded-full bg-indigo-600 px-2 py-0.5 text-xs font-bold text-white", title: product.groupTitle ? `Part of set: ${product.groupTitle}` : "Part of a set", children: product.isGroupParent ? "Set Parent" : "In Set" })), product.sublistingCategoryId && (_jsx(Span, { className: "rounded-full bg-sky-600 px-2 py-0.5 text-xs font-bold text-white", title: "Has variants or sub-listings", children: "Has Variants" }))] }), onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (event) => {
|
|
55
55
|
event.stopPropagation();
|
|
56
56
|
onSelect(product.id, !isSelected);
|
|
57
57
|
}, className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" }))] }), _jsx(BaseListingCard.Info, { variant: variant, children: variant === "list" ? (
|
|
@@ -92,6 +92,7 @@ export function PreOrdersIndexListing({ initialData, categorySlug, brandName })
|
|
|
92
92
|
setSearchInput("");
|
|
93
93
|
}, [table]);
|
|
94
94
|
const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
|
|
95
|
+
const pendingFilterCount = FILTER_KEYS.filter((k) => !!pendingFilters[k]).length;
|
|
95
96
|
const hasActiveState = !!table.get(TABLE_KEYS.QUERY) ||
|
|
96
97
|
table.get(TABLE_KEYS.SHOW_CLOSED) === "true" ||
|
|
97
98
|
table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT ||
|
|
@@ -200,5 +201,5 @@ export function PreOrdersIndexListing({ initialData, categorySlug, brandName })
|
|
|
200
201
|
] }), 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: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx("div", { className: "py-6", children: isLoading ? (_jsx("div", { className: gridClass, children: Array.from({ length: 10 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-square bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-3 space-y-2", children: [_jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" }), _jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-1/3" }), _jsx("div", { className: "h-8 bg-zinc-200 dark:bg-slate-700 rounded" })] })] }, i))) })) : preOrders.length === 0 ? (_jsx("p", { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "No pre-orders found." })) : view === "list" ? (_jsx("div", { className: "flex flex-col divide-y divide-zinc-100 dark:divide-zinc-800 rounded-xl border border-zinc-100 dark:border-zinc-800", children: preOrders.map((product) => (_jsx(MarketplacePreorderCard, { product: product, variant: "list", hrefBuilder: (p) => String(ROUTES.PUBLIC.PRE_ORDER_DETAIL(p.id)), onAddToCart: handleAddToCart, wishlistActions: wishlistActions, selectable: selection.isSelecting, isSelected: selection.isSelected(product.id), onSelect: (id) => selection.toggle(id) }, product.id))) })) : (_jsx("div", { className: gridClass, children: preOrders.map((product) => (_jsx(MarketplacePreorderCard, { product: product, variant: "grid", hrefBuilder: (p) => String(ROUTES.PUBLIC.PRE_ORDER_DETAIL(p.id)), onAddToCart: handleAddToCart, wishlistActions: wishlistActions, selectable: selection.isSelecting, isSelected: selection.isSelected(product.id), onSelect: (id) => selection.toggle(id) }, product.id))) })) }), _jsx(CompareOverlay, { isOpen: compareIds.length > 0, productIds: compareIds, productType: "preorder", onClose: () => {
|
|
201
202
|
setCompareIds([]);
|
|
202
203
|
selection.clearSelection();
|
|
203
|
-
}, onRemove: (id) => setCompareIds((ids) => ids.filter((i) => i !== 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: [_jsxs("span", { className: "flex items-center gap-2 text-base font-semibold text-zinc-900 dark:text-zinc-100", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Filters"] }), _jsxs("div", { className: "flex items-center gap-2", children: [
|
|
204
|
+
}, onRemove: (id) => setCompareIds((ids) => ids.filter((i) => i !== 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: [_jsxs("span", { className: "flex items-center gap-2 text-base font-semibold text-zinc-900 dark:text-zinc-100", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Filters"] }), _jsxs("div", { className: "flex items-center gap-2", children: [pendingFilterCount > 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 filters", 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(PreOrderFilters, { table: pendingTable, currencyPrefix: "\u20B9", categoryOptions: categoryOptions, brandOptions: brandOptions }) }), _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", pendingFilterCount > 0 ? ` (${pendingFilterCount})` : ""] }) })] })] }))] }));
|
|
204
205
|
}
|
|
@@ -86,6 +86,7 @@ export function AuctionsIndexListing({ initialData, categorySlug, brandName }) {
|
|
|
86
86
|
setSearchInput("");
|
|
87
87
|
}, [table]);
|
|
88
88
|
const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
|
|
89
|
+
const pendingFilterCount = FILTER_KEYS.filter((k) => !!pendingFilters[k]).length;
|
|
89
90
|
const hasActiveState = !!table.get(TABLE_KEYS.QUERY) ||
|
|
90
91
|
table.get(TABLE_KEYS.SHOW_ENDED) === "true" ||
|
|
91
92
|
table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT ||
|
|
@@ -165,5 +166,5 @@ export function AuctionsIndexListing({ initialData, categorySlug, brandName }) {
|
|
|
165
166
|
selection.clearSelection();
|
|
166
167
|
},
|
|
167
168
|
},
|
|
168
|
-
] }), 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: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx("div", { className: "py-6", children: isLoading ? (_jsx("div", { className: gridClass, children: Array.from({ length: 8 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-square bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-3 space-y-2", children: [_jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-full" }), _jsx("div", { className: "h-8 bg-zinc-200 dark:bg-slate-700 rounded" })] })] }, i))) })) : (_jsx(MarketplaceAuctionGrid, { auctions: auctions, variant: view === "list" ? "list" : "grid", gridClassName: view === "list" ? "flex flex-col gap-4" : gridClass, wishlistActions: wishlistActions })) }), 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: [_jsxs("span", { className: "flex items-center gap-2 text-base font-semibold text-zinc-900 dark:text-zinc-100", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Filters"] }), _jsxs("div", { className: "flex items-center gap-2", children: [
|
|
169
|
+
] }), 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: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx("div", { className: "py-6", children: isLoading ? (_jsx("div", { className: gridClass, children: Array.from({ length: 8 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-square bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-3 space-y-2", children: [_jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-full" }), _jsx("div", { className: "h-8 bg-zinc-200 dark:bg-slate-700 rounded" })] })] }, i))) })) : (_jsx(MarketplaceAuctionGrid, { auctions: auctions, variant: view === "list" ? "list" : "grid", gridClassName: view === "list" ? "flex flex-col gap-4" : gridClass, wishlistActions: wishlistActions })) }), 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: [_jsxs("span", { className: "flex items-center gap-2 text-base font-semibold text-zinc-900 dark:text-zinc-100", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Filters"] }), _jsxs("div", { className: "flex items-center gap-2", children: [pendingFilterCount > 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 filters", 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(AuctionFilters, { table: pendingTable, currencyPrefix: "\u20B9", categoryOptions: categoryOptions, brandOptions: brandOptions }) }), _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", pendingFilterCount > 0 ? ` (${pendingFilterCount})` : ""] }) })] })] }))] }));
|
|
169
170
|
}
|
|
@@ -263,5 +263,7 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
|
|
|
263
263
|
id: s.id,
|
|
264
264
|
label: s.title,
|
|
265
265
|
content: _jsx(CustomSectionTabContent, { section: s }),
|
|
266
|
-
})) })),
|
|
266
|
+
})) })), renderBundleSection: bundleMemberships.length > 0
|
|
267
|
+
? () => (_jsxs("div", { className: "rounded-xl border border-teal-200 dark:border-teal-800/60 bg-teal-50/60 dark:bg-teal-900/20 p-5", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx(Span, { className: "text-teal-600 dark:text-teal-400 text-base", "aria-hidden": "true", children: "\uD83D\uDCE6" }), _jsxs(Text, { className: "text-sm font-semibold text-teal-800 dark:text-teal-200", children: ["Part of ", bundleMemberships.length === 1 ? "a bundle" : `${bundleMemberships.length} bundles`] })] }), _jsx("div", { className: "flex flex-col gap-3", children: bundleMemberships.map((b) => (_jsxs("div", { className: "flex items-center justify-between gap-4 rounded-lg border border-teal-200 dark:border-teal-800/50 bg-white dark:bg-teal-900/30 px-4 py-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsx(Text, { className: "text-xs text-teal-600 dark:text-teal-400 font-medium uppercase tracking-wide mb-0.5", children: "Included in bundle" }), _jsx(Text, { className: "text-sm font-semibold text-zinc-800 dark:text-zinc-100 truncate", children: b.title })] }), _jsx(Link, { href: String(ROUTES.PUBLIC.BUNDLE_DETAIL(b.id)), className: "flex-shrink-0 rounded-lg bg-teal-600 hover:bg-teal-700 px-3 py-1.5 text-xs font-semibold text-white transition-colors", children: "View Bundle \u2192" })] }, b.id))) })] }))
|
|
268
|
+
: undefined, renderRelated: () => relatedItems.length > 0 ? (_jsx(RelatedProductsCarousel, { items: relatedItems })) : null }), _jsxs(BuyBar, { children: [formattedPrice && (_jsx(Span, { className: "mr-auto text-sm font-bold text-zinc-900 dark:text-zinc-50", children: formattedPrice })), _jsx(Button, { variant: "secondary", size: "sm", className: "shrink-0", disabled: !inStock, children: "Add to Cart" }), _jsx(Button, { variant: "primary", size: "sm", className: "flex-1", disabled: !inStock, children: inStock ? "Buy Now" : "Out of Stock" })] })] })] }));
|
|
267
269
|
}
|
|
@@ -13,6 +13,8 @@ export interface ProductDetailViewProps extends Omit<DetailViewShellProps, "main
|
|
|
13
13
|
/** Rendered below the sub-listing section and above the tabs (e.g. group carousel). */
|
|
14
14
|
renderGroupSection?: () => React.ReactNode;
|
|
15
15
|
renderTabs?: () => React.ReactNode;
|
|
16
|
+
/** Bundle membership section rendered below tabs and above related products. */
|
|
17
|
+
renderBundleSection?: () => React.ReactNode;
|
|
16
18
|
renderRelated?: () => React.ReactNode;
|
|
17
19
|
/**
|
|
18
20
|
* When true (default), the action rail (col 3) becomes sticky on desktop so
|
|
@@ -25,4 +27,4 @@ export interface ProductDetailViewProps extends Omit<DetailViewShellProps, "main
|
|
|
25
27
|
*/
|
|
26
28
|
stickyRailOffset?: string;
|
|
27
29
|
}
|
|
28
|
-
export declare function ProductDetailView({ renderGallery, renderInfo, renderActions, renderSublistingSection, renderGroupSection, renderTabs, renderRelated, isLoading, stickyActionRail, stickyRailOffset, ...rest }: ProductDetailViewProps): import("react/jsx-runtime").JSX.Element;
|
|
30
|
+
export declare function ProductDetailView({ renderGallery, renderInfo, renderActions, renderSublistingSection, renderGroupSection, renderTabs, renderBundleSection, renderRelated, isLoading, stickyActionRail, stickyRailOffset, ...rest }: ProductDetailViewProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { DetailViewShell } from "../../../ui";
|
|
4
|
-
export function ProductDetailView({ renderGallery, renderInfo, renderActions, renderSublistingSection, renderGroupSection, renderTabs, renderRelated, isLoading = false, stickyActionRail = true, stickyRailOffset = "top-20", ...rest }) {
|
|
4
|
+
export function ProductDetailView({ renderGallery, renderInfo, renderActions, renderSublistingSection, renderGroupSection, renderTabs, renderBundleSection, renderRelated, isLoading = false, stickyActionRail = true, stickyRailOffset = "top-20", ...rest }) {
|
|
5
5
|
const sublistingNode = renderSublistingSection?.();
|
|
6
6
|
const groupNode = renderGroupSection?.();
|
|
7
7
|
const afterMainNode = sublistingNode || groupNode ? (_jsxs(React.Fragment, { children: [sublistingNode, groupNode] })) : undefined;
|
|
@@ -9,5 +9,5 @@ export function ProductDetailView({ renderGallery, renderInfo, renderActions, re
|
|
|
9
9
|
renderGallery?.(isLoading),
|
|
10
10
|
renderInfo?.(isLoading),
|
|
11
11
|
renderActions?.(),
|
|
12
|
-
], afterMain: afterMainNode, belowFold: [renderTabs?.(), renderRelated?.()] }));
|
|
12
|
+
], afterMain: afterMainNode, belowFold: [renderTabs?.(), renderBundleSection?.(), renderRelated?.()] }));
|
|
13
13
|
}
|
|
@@ -3,9 +3,9 @@ import type { UrlTable } from "../../filters/FilterPanel";
|
|
|
3
3
|
export type { FacetOption, UrlTable };
|
|
4
4
|
export type ProductFilterVariant = "admin" | "seller" | "public";
|
|
5
5
|
export declare const PRODUCT_FILTER_KEYS: {
|
|
6
|
-
readonly admin: readonly ["category", "condition", "minPrice", "maxPrice", "brand", "seller", "tags", "status"];
|
|
7
|
-
readonly seller: readonly ["category", "condition", "minPrice", "maxPrice", "brand", "tags", "status"];
|
|
8
|
-
readonly public: readonly ["category", "condition", "minPrice", "maxPrice", "brand", "storeId", "tags"];
|
|
6
|
+
readonly admin: readonly ["category", "condition", "minPrice", "maxPrice", "brand", "seller", "tags", "status", "sublistingCategory", "isPartOfBundle", "features"];
|
|
7
|
+
readonly seller: readonly ["category", "condition", "minPrice", "maxPrice", "brand", "tags", "status", "sublistingCategory", "isPartOfBundle", "features"];
|
|
8
|
+
readonly public: readonly ["category", "condition", "minPrice", "maxPrice", "brand", "storeId", "tags", "sublistingCategory", "isPartOfBundle", "features"];
|
|
9
9
|
};
|
|
10
10
|
export declare const PRODUCT_ADMIN_SORT_OPTIONS: readonly [{
|
|
11
11
|
readonly value: string;
|
|
@@ -81,11 +81,17 @@ export interface ProductFiltersProps {
|
|
|
81
81
|
/** @deprecated use storeOptions */
|
|
82
82
|
sellerOptions?: FacetOption[];
|
|
83
83
|
tagOptions?: FacetOption[];
|
|
84
|
+
/** Sublisting category options — separate from main category filter */
|
|
85
|
+
sublistingCategoryOptions?: FacetOption[];
|
|
86
|
+
/** Feature badge options for filtering by product features */
|
|
87
|
+
featureOptions?: FacetOption[];
|
|
84
88
|
showStatus?: boolean;
|
|
85
89
|
variant?: ProductFilterVariant;
|
|
86
90
|
statusOptions?: FacetOption[];
|
|
87
91
|
currencyPrefix?: string;
|
|
88
92
|
/** Show free-shipping toggle */
|
|
89
93
|
showShipping?: boolean;
|
|
94
|
+
/** Show "part of a bundle" switch filter (default true) */
|
|
95
|
+
showBundleFilter?: boolean;
|
|
90
96
|
}
|
|
91
|
-
export declare function ProductFilters({ table, categoryOptions, brandOptions, storeOptions, sellerOptions, tagOptions, showStatus, variant, statusOptions, currencyPrefix, showShipping, }: ProductFiltersProps): import("react/jsx-runtime").JSX.Element;
|
|
97
|
+
export declare function ProductFilters({ table, categoryOptions, brandOptions, storeOptions, sellerOptions, tagOptions, sublistingCategoryOptions, featureOptions, showStatus, variant, statusOptions, currencyPrefix, showShipping, showBundleFilter, }: ProductFiltersProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -18,6 +18,9 @@ export const PRODUCT_FILTER_KEYS = {
|
|
|
18
18
|
TABLE_KEYS.SELLER,
|
|
19
19
|
TABLE_KEYS.TAGS,
|
|
20
20
|
TABLE_KEYS.STATUS,
|
|
21
|
+
TABLE_KEYS.SUBLISTING_CATEGORY,
|
|
22
|
+
TABLE_KEYS.IS_PART_OF_BUNDLE,
|
|
23
|
+
TABLE_KEYS.FEATURES,
|
|
21
24
|
],
|
|
22
25
|
seller: [
|
|
23
26
|
TABLE_KEYS.CATEGORY,
|
|
@@ -27,6 +30,9 @@ export const PRODUCT_FILTER_KEYS = {
|
|
|
27
30
|
TABLE_KEYS.BRAND,
|
|
28
31
|
TABLE_KEYS.TAGS,
|
|
29
32
|
TABLE_KEYS.STATUS,
|
|
33
|
+
TABLE_KEYS.SUBLISTING_CATEGORY,
|
|
34
|
+
TABLE_KEYS.IS_PART_OF_BUNDLE,
|
|
35
|
+
TABLE_KEYS.FEATURES,
|
|
30
36
|
],
|
|
31
37
|
public: [
|
|
32
38
|
TABLE_KEYS.CATEGORY,
|
|
@@ -36,6 +42,9 @@ export const PRODUCT_FILTER_KEYS = {
|
|
|
36
42
|
TABLE_KEYS.BRAND,
|
|
37
43
|
TABLE_KEYS.STORE_ID,
|
|
38
44
|
TABLE_KEYS.TAGS,
|
|
45
|
+
TABLE_KEYS.SUBLISTING_CATEGORY,
|
|
46
|
+
TABLE_KEYS.IS_PART_OF_BUNDLE,
|
|
47
|
+
TABLE_KEYS.FEATURES,
|
|
39
48
|
],
|
|
40
49
|
};
|
|
41
50
|
export const PRODUCT_ADMIN_SORT_OPTIONS = [
|
|
@@ -78,7 +87,7 @@ export function getProductSortOptions(variant) {
|
|
|
78
87
|
return PRODUCT_PUBLIC_SORT_OPTIONS;
|
|
79
88
|
}
|
|
80
89
|
}
|
|
81
|
-
export function ProductFilters({ table, categoryOptions = [], brandOptions = [], storeOptions = [], sellerOptions, tagOptions = [], showStatus = false, variant, statusOptions, currencyPrefix = "", showShipping = true, }) {
|
|
90
|
+
export function ProductFilters({ table, categoryOptions = [], brandOptions = [], storeOptions = [], sellerOptions, tagOptions = [], sublistingCategoryOptions = [], featureOptions = [], showStatus = false, variant, statusOptions, currencyPrefix = "", showShipping = true, showBundleFilter = true, }) {
|
|
82
91
|
const resolvedStoreOptions = storeOptions.length > 0 ? storeOptions : (sellerOptions ?? []);
|
|
83
92
|
const t = useTranslations("filters");
|
|
84
93
|
const conditionOptions = [
|
|
@@ -92,21 +101,16 @@ export function ProductFilters({ table, categoryOptions = [], brandOptions = [],
|
|
|
92
101
|
{ value: PRODUCT_FIELDS.STATUS_VALUES.DRAFT, label: t("statusDraft") },
|
|
93
102
|
{ value: PRODUCT_FIELDS.STATUS_VALUES.OUT_OF_STOCK, label: t("statusOutOfStock") },
|
|
94
103
|
];
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
? table.get(TABLE_KEYS.TAGS).split("|").filter(Boolean)
|
|
105
|
-
: [];
|
|
106
|
-
const selectedStatuses = table.get(TABLE_KEYS.STATUS)
|
|
107
|
-
? table.get(TABLE_KEYS.STATUS).split("|").filter(Boolean)
|
|
108
|
-
: [];
|
|
104
|
+
const splitPipe = (key) => table.get(key) ? table.get(key).split("|").filter(Boolean) : [];
|
|
105
|
+
const selectedCategories = splitPipe(TABLE_KEYS.CATEGORY);
|
|
106
|
+
const selectedBrands = splitPipe(TABLE_KEYS.BRAND);
|
|
107
|
+
const selectedSellers = splitPipe(TABLE_KEYS.STORE_ID);
|
|
108
|
+
const selectedConditions = splitPipe(TABLE_KEYS.CONDITION);
|
|
109
|
+
const selectedTags = splitPipe(TABLE_KEYS.TAGS);
|
|
110
|
+
const selectedStatuses = splitPipe(TABLE_KEYS.STATUS);
|
|
111
|
+
const selectedSublistings = splitPipe(TABLE_KEYS.SUBLISTING_CATEGORY);
|
|
112
|
+
const selectedFeatures = splitPipe(TABLE_KEYS.FEATURES);
|
|
109
113
|
const resolvedVariant = variant ?? (showStatus ? "admin" : "public");
|
|
110
114
|
const shouldShowStatus = resolvedVariant !== "public" || showStatus;
|
|
111
|
-
return (_jsxs(Div, { children: [categoryOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("category"), options: categoryOptions, selected: selectedCategories, onChange: (vals) => table.set(TABLE_KEYS.CATEGORY, vals.join("|")), searchable: true, defaultCollapsed: categoryOptions.length > 6 })), _jsx(FilterFacetSection, { title: t("condition"), options: conditionOptions, selected: selectedConditions, onChange: (vals) => table.set(TABLE_KEYS.CONDITION, vals.join("|")), searchable: false, defaultCollapsed: false }), _jsx(RangeFilter, { title: t("priceRange"), minValue: table.get(TABLE_KEYS.MIN_PRICE), maxValue: table.get(TABLE_KEYS.MAX_PRICE), onMinChange: (v) => table.set(TABLE_KEYS.MIN_PRICE, v), onMaxChange: (v) => table.set(TABLE_KEYS.MAX_PRICE, v), prefix: currencyPrefix, showSlider: true, minBound: 0, maxBound: 500000, step: 500, minPlaceholder: t("minPrice"), maxPlaceholder: t("maxPrice"), defaultCollapsed: false }), brandOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("brand"), options: brandOptions, selected: selectedBrands, onChange: (vals) => table.set(TABLE_KEYS.BRAND, vals
|
|
115
|
+
return (_jsxs(Div, { children: [categoryOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("category"), options: categoryOptions, selected: selectedCategories, onChange: (vals) => table.set(TABLE_KEYS.CATEGORY, vals.join("|")), searchable: true, defaultCollapsed: categoryOptions.length > 6 })), _jsx(FilterFacetSection, { title: t("condition"), options: conditionOptions, selected: selectedConditions, onChange: (vals) => table.set(TABLE_KEYS.CONDITION, vals.join("|")), searchable: false, defaultCollapsed: false }), _jsx(RangeFilter, { title: t("priceRange"), minValue: table.get(TABLE_KEYS.MIN_PRICE), maxValue: table.get(TABLE_KEYS.MAX_PRICE), onMinChange: (v) => table.set(TABLE_KEYS.MIN_PRICE, v), onMaxChange: (v) => table.set(TABLE_KEYS.MAX_PRICE, v), prefix: currencyPrefix, showSlider: true, minBound: 0, maxBound: 500000, step: 500, minPlaceholder: t("minPrice"), maxPlaceholder: t("maxPrice"), defaultCollapsed: false }), brandOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("brand"), options: brandOptions, selected: selectedBrands, onChange: (vals) => table.set(TABLE_KEYS.BRAND, vals.join("|")), searchable: brandOptions.length > 4, defaultCollapsed: brandOptions.length > 6 })), resolvedStoreOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("store"), options: resolvedStoreOptions, selected: selectedSellers, onChange: (vals) => table.set(TABLE_KEYS.STORE_ID, vals.join("|")), searchable: resolvedStoreOptions.length > 4, defaultCollapsed: resolvedStoreOptions.length > 6 })), showShipping && (_jsx(SwitchFilter, { title: t("shipping"), label: t("freeShippingOnly"), checked: table.get(TABLE_KEYS.FREE_SHIPPING) === "true", onChange: (v) => table.set(TABLE_KEYS.FREE_SHIPPING, v ? "true" : ""), defaultCollapsed: false })), tagOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("tags"), options: tagOptions, selected: selectedTags, onChange: (vals) => table.set(TABLE_KEYS.TAGS, vals.join("|")), searchable: tagOptions.length > 4, defaultCollapsed: tagOptions.length > 6 })), shouldShowStatus && (_jsx(FilterFacetSection, { title: t("status"), options: statusOptions ?? defaultStatusOptions, selected: selectedStatuses, onChange: (vals) => table.set(TABLE_KEYS.STATUS, vals.join("|")), searchable: false, defaultCollapsed: false })), sublistingCategoryOptions.length > 0 && (_jsx(FilterFacetSection, { title: "Sublisting Type", options: sublistingCategoryOptions, selected: selectedSublistings, onChange: (vals) => table.set(TABLE_KEYS.SUBLISTING_CATEGORY, vals.join("|")), searchable: sublistingCategoryOptions.length > 4, defaultCollapsed: false })), showBundleFilter && (_jsx(SwitchFilter, { title: "Bundles", label: "Part of a bundle only", checked: table.get(TABLE_KEYS.IS_PART_OF_BUNDLE) === "true", onChange: (v) => table.set(TABLE_KEYS.IS_PART_OF_BUNDLE, v ? "true" : ""), defaultCollapsed: false })), featureOptions.length > 0 && (_jsx(FilterFacetSection, { title: "Features", options: featureOptions, selected: selectedFeatures, onChange: (vals) => table.set(TABLE_KEYS.FEATURES, vals.join("|")), searchable: featureOptions.length > 4, defaultCollapsed: featureOptions.length > 4 }))] }));
|
|
112
116
|
}
|
|
@@ -49,7 +49,7 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
|
|
|
49
49
|
.filter(Boolean)
|
|
50
50
|
.join(" "), children: [_jsxs(Div, { className: "relative overflow-hidden bg-zinc-100 dark:bg-slate-800 aspect-square", children: [product.mainImage ? (_jsx(MediaImage, { src: product.mainImage, alt: product.title, size: "card", className: "transition-transform duration-500 group-hover:scale-105" })) : (_jsx(Div, { className: "h-full w-full flex items-center justify-center bg-gradient-to-br from-zinc-100 to-zinc-200 dark:from-slate-800 dark:to-slate-700", children: _jsx(Span, { className: "text-4xl opacity-30", children: "\uD83D\uDECD\uFE0F" }) })), onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onSelect(product.id); }, label: isSelected ? "Deselect" : "Select", position: "top-2 left-2", className: selectionMode || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), _jsx(Div, { className: `absolute top-2 flex flex-col gap-1 ${onSelect ? "left-10" : "left-2"}`, children: !selectionMode && !isSelected && (_jsxs(_Fragment, { children: [discount && (_jsxs(Span, { className: "rounded-full bg-rose-500 px-2 py-0.5 text-[10px] font-bold text-white shadow-sm", children: ["-", discount, "%"] })), typeBadge && (_jsx(Span, { className: `rounded-full px-2 py-0.5 text-[10px] font-bold shadow-sm ${typeBadge.cls}`, children: typeBadge.label })), product.partOfBundleIds && product.partOfBundleIds.length > 0 && (_jsx(Span, { className: "rounded-full bg-teal-600 px-2 py-0.5 text-[10px] font-bold text-white shadow-sm", title: product.partOfBundleTitles && product.partOfBundleTitles.length > 0
|
|
51
51
|
? `In bundle: ${product.partOfBundleTitles[0]}`
|
|
52
|
-
: "In a bundle", children: "Bundled" }))] })) }), onAddToWishlist && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: (e) => {
|
|
52
|
+
: "In a bundle", children: "Bundled" })), product.groupId && (_jsx(Span, { className: "rounded-full bg-indigo-600 px-2 py-0.5 text-[10px] font-bold text-white shadow-sm", title: product.groupTitle ? `Part of set: ${product.groupTitle}` : "Part of a set", children: product.isGroupParent ? "Set Parent" : "In Set" })), product.sublistingCategoryId && (_jsx(Span, { className: "rounded-full bg-sky-600 px-2 py-0.5 text-[10px] font-bold text-white shadow-sm", title: "Has variants or sub-listings", children: "Has Variants" }))] })) }), onAddToWishlist && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: (e) => {
|
|
53
53
|
e.stopPropagation();
|
|
54
54
|
e.preventDefault();
|
|
55
55
|
onAddToWishlist(product.id);
|
|
@@ -59,7 +59,7 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
|
|
|
59
59
|
isWishlisted
|
|
60
60
|
? "bg-rose-500 text-white hover:bg-rose-600"
|
|
61
61
|
: "bg-white/90 dark:bg-slate-800/90 text-zinc-500 dark:text-zinc-400 hover:text-rose-500 hover:bg-white dark:hover:bg-slate-800",
|
|
62
|
-
].join(" "), children: _jsx("svg", { className: "h-4 w-4", fill: isWishlisted ? "currentColor" : "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z" }) }) }))] }), _jsxs(Div, { className: "flex flex-1 flex-col p-3 pt-2.5", children: [_jsx(Text, { className: `${THEME_CONSTANTS.utilities.textClamp2} text-sm font-semibold text-zinc-900 dark:text-white leading-snug`, children: product.title }), (product.categoryName || product.brand) && (_jsxs(Row, { className: "mt-1 gap-1 flex-wrap", children: [product.categoryName && (
|
|
62
|
+
].join(" "), children: _jsx("svg", { className: "h-4 w-4", fill: isWishlisted ? "currentColor" : "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z" }) }) }))] }), _jsxs(Div, { className: "flex flex-1 flex-col p-3 pt-2.5", children: [_jsx(Text, { className: `${THEME_CONSTANTS.utilities.textClamp2} text-sm font-semibold text-zinc-900 dark:text-white leading-snug`, children: product.title }), (product.categoryName || product.brand) && (_jsxs(Row, { className: "mt-1 gap-1 flex-wrap", children: [product.categoryName && (_jsxs(Span, { className: "inline-flex items-center gap-0.5 rounded-full bg-zinc-100 dark:bg-zinc-800 px-1.5 py-0.5 text-[10px] text-zinc-500 dark:text-zinc-400 truncate max-w-[100px]", children: [_jsx("svg", { className: "h-2.5 w-2.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 7a2 2 0 012-2h14a2 2 0 012 2v10a2 2 0 01-2 2H5a2 2 0 01-2-2V7z" }) }), product.categoryName] })), product.brand && (_jsxs(Span, { className: "inline-flex items-center gap-0.5 rounded-full bg-zinc-100 dark:bg-zinc-800 px-1.5 py-0.5 text-[10px] text-zinc-500 dark:text-zinc-400 truncate max-w-[90px]", children: [_jsx("svg", { className: "h-2.5 w-2.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" }) }), product.brand] }))] })), (() => {
|
|
63
63
|
const seller = safeDisplayName(product.storeName, "");
|
|
64
64
|
return seller ? (_jsxs(Text, { className: "mt-0.5 text-[11px] text-zinc-400 dark:text-zinc-500 truncate", children: ["by ", seller] })) : null;
|
|
65
65
|
})(), product.rating !== undefined && (_jsxs(Row, { className: "mt-1 gap-1 items-center", children: [_jsx(Span, { className: "text-[11px] text-amber-400", children: "\u2605" }), _jsxs(Span, { className: "text-[11px] text-zinc-500 dark:text-zinc-400", children: [product.rating.toFixed(1), product.reviewCount ? ` (${product.reviewCount})` : ""] })] })), _jsxs(Div, { className: "mt-auto pt-2", children: [_jsxs(Row, { className: "items-baseline gap-2", children: [_jsx(Span, { className: "text-base font-bold text-primary dark:text-primary-400", children: formatCurrency(product.price, getDefaultCurrency()) }), product.originalPrice && product.originalPrice > product.price && (_jsx(Span, { className: "text-xs text-zinc-400 line-through dark:text-zinc-500", children: formatCurrency(product.originalPrice, getDefaultCurrency()) }))] }), (() => {
|
|
@@ -79,6 +79,7 @@ export function ProductsIndexListing({ initialData }) {
|
|
|
79
79
|
setSearchInput("");
|
|
80
80
|
}, [table]);
|
|
81
81
|
const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
|
|
82
|
+
const pendingFilterCount = FILTER_KEYS.filter((k) => !!pendingFilters[k]).length;
|
|
82
83
|
const hasActiveState = !!table.get(TABLE_KEYS.QUERY) ||
|
|
83
84
|
table.get(TABLE_KEYS.SHOW_SOLD) === "true" ||
|
|
84
85
|
table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT ||
|
|
@@ -196,5 +197,5 @@ export function ProductsIndexListing({ initialData }) {
|
|
|
196
197
|
] }), 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: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx("div", { className: "py-6", children: isLoading ? (_jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4", children: Array.from({ length: 10 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-square bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-3 space-y-2", children: [_jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" }), _jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-1/3" })] })] }, i))) })) : (_jsx(ProductGrid, { products: products, getProductHref: (p) => String(ROUTES.PUBLIC.PRODUCT_DETAIL(p.slug || p.id)), view: view === "grid" ? "card" : "list", onWishlistToggle: handleWishlistToggle, wishlistedIds: wishlistedIds, onAddToCart: handleAddToCart, onBuyNow: handleBuyNow, selectionMode: selection.isSelecting, selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle })) }), _jsx(CompareOverlay, { isOpen: compareIds.length > 0, productIds: compareIds, productType: "product", onClose: () => {
|
|
197
198
|
setCompareIds([]);
|
|
198
199
|
selection.clearSelection();
|
|
199
|
-
}, onRemove: (id) => setCompareIds((ids) => ids.filter((i) => i !== 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: [_jsxs("span", { className: "flex items-center gap-2 text-base font-semibold text-zinc-900 dark:text-zinc-100", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Filters"] }), _jsxs("div", { className: "flex items-center gap-2", children: [
|
|
200
|
+
}, onRemove: (id) => setCompareIds((ids) => ids.filter((i) => i !== 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: [_jsxs("span", { className: "flex items-center gap-2 text-base font-semibold text-zinc-900 dark:text-zinc-100", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Filters"] }), _jsxs("div", { className: "flex items-center gap-2", children: [pendingFilterCount > 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 filters", 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(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9", categoryOptions: categoryOptions }) }), _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", pendingFilterCount > 0 ? ` (${pendingFilterCount})` : ""] }) })] })] }))] }));
|
|
200
201
|
}
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* action-defs.ts
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Universal CTA registry for the entire platform.
|
|
5
|
+
* Every action — public marketplace CTAs, dashboard row actions, quick actions — is
|
|
6
|
+
* registered here with auth requirements, RBAC permissions, and enable/disable defaults.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* Dashboard quick bar — top-level shortcut buttons per dashboard type
|
|
12
|
-
*
|
|
13
|
-
* Pure TypeScript — no React, no JSX, no callbacks.
|
|
14
|
-
* Consuming components resolve iconName → Lucide component, and supply onClick.
|
|
8
|
+
* Three enforcement layers read from this registry:
|
|
9
|
+
* Client — useAuthGate() shows LoginRequiredModal for Tier 1 (public) actions
|
|
10
|
+
* Server — checkActionAllowed() enforces the same guards on every server action
|
|
11
|
+
* Admin — ActionPermissionsManager toggles defaultEnabled via siteSettings.actionConfig
|
|
15
12
|
*/
|
|
16
13
|
export type ActionVariant = "primary" | "secondary" | "outline" | "ghost" | "danger" | "warning";
|
|
17
14
|
export declare const ACTION_ID: {
|
|
@@ -25,8 +22,29 @@ export declare const ACTION_ID: {
|
|
|
25
22
|
readonly PLACE_BID: "place-bid";
|
|
26
23
|
readonly BUY_NOW_AUCTION: "buy-now-auction";
|
|
27
24
|
readonly WATCH_AUCTION: "watch-auction";
|
|
25
|
+
readonly UNWATCH_AUCTION: "unwatch-auction";
|
|
28
26
|
readonly RESERVE_NOW: "reserve-now";
|
|
29
27
|
readonly CANCEL_RESERVATION: "cancel-reservation";
|
|
28
|
+
readonly CHECKOUT: "checkout";
|
|
29
|
+
readonly ENTER_PRIZE_DRAW: "enter-prize-draw";
|
|
30
|
+
readonly ENTER_RAFFLE: "enter-raffle";
|
|
31
|
+
readonly REPORT_LISTING: "report-listing";
|
|
32
|
+
readonly FOLLOW_STORE: "follow-store";
|
|
33
|
+
readonly WRITE_REVIEW: "write-review";
|
|
34
|
+
readonly MESSAGE_SELLER: "message-seller";
|
|
35
|
+
readonly BECOME_SELLER: "become-seller";
|
|
36
|
+
readonly REQUEST_PAYOUT: "request-payout";
|
|
37
|
+
readonly RESPOND_TO_REVIEW: "respond-to-review";
|
|
38
|
+
readonly CANCEL_ORDER: "cancel-order";
|
|
39
|
+
readonly REQUEST_RETURN: "request-return";
|
|
40
|
+
readonly REORDER: "reorder";
|
|
41
|
+
readonly TRACK_ORDER: "track-order";
|
|
42
|
+
readonly EDIT_PROFILE: "edit-profile";
|
|
43
|
+
readonly ADD_ADDRESS: "add-address";
|
|
44
|
+
readonly EDIT_ADDRESS: "edit-address";
|
|
45
|
+
readonly DELETE_ADDRESS: "delete-address";
|
|
46
|
+
readonly CHANGE_PASSWORD: "change-password";
|
|
47
|
+
readonly DELETE_ACCOUNT: "delete-account";
|
|
30
48
|
};
|
|
31
49
|
export type ActionId = (typeof ACTION_ID)[keyof typeof ACTION_ID];
|
|
32
50
|
export interface ActionMeta {
|
|
@@ -37,8 +55,26 @@ export interface ActionMeta {
|
|
|
37
55
|
variant: ActionVariant;
|
|
38
56
|
/** Lucide icon name — consuming component resolves this to the React component */
|
|
39
57
|
iconName?: string;
|
|
40
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Tier 1: shows LoginRequiredModal on public pages if user is not signed in.
|
|
60
|
+
* Server action also enforces via checkActionAllowed().
|
|
61
|
+
*/
|
|
41
62
|
requiresAuth?: boolean;
|
|
63
|
+
/** Message shown in LoginRequiredModal. Required when requiresAuth=true. */
|
|
64
|
+
authMessage?: string;
|
|
65
|
+
/**
|
|
66
|
+
* RBAC permission key required to use this action.
|
|
67
|
+
* If set, user must have this permission in addition to being signed in.
|
|
68
|
+
* Uses the 85+ permission keys from the RBAC system.
|
|
69
|
+
* Example: "products.create", "orders.refund", "admin.users.suspend"
|
|
70
|
+
*/
|
|
71
|
+
requiredPermission?: string;
|
|
72
|
+
/**
|
|
73
|
+
* Whether this action is enabled by default.
|
|
74
|
+
* Can be overridden at runtime via siteSettings.actionConfig[actionId].enabled.
|
|
75
|
+
* Defaults to true when omitted.
|
|
76
|
+
*/
|
|
77
|
+
defaultEnabled?: boolean;
|
|
42
78
|
}
|
|
43
79
|
export declare const ACTION_META: Record<ActionId, ActionMeta>;
|
|
44
80
|
export declare const DETAIL_ACTIONS: {
|
|
@@ -56,7 +92,7 @@ export declare const MOBILE_PRIMARY_ACTIONS: {
|
|
|
56
92
|
};
|
|
57
93
|
export declare const LISTING_BULK_ACTIONS: {
|
|
58
94
|
readonly products: readonly ["add-to-cart", "add-to-wishlist", "compare"];
|
|
59
|
-
readonly auctions: readonly ["watch-auction", "
|
|
95
|
+
readonly auctions: readonly ["watch-auction", "unwatch-auction", "compare"];
|
|
60
96
|
readonly preorders: readonly ["add-to-cart", "add-to-wishlist", "compare"];
|
|
61
97
|
readonly stores: ActionId[];
|
|
62
98
|
};
|
|
@@ -92,6 +128,14 @@ export interface RowActionMeta {
|
|
|
92
128
|
destructive?: boolean;
|
|
93
129
|
/** Adds a separator above this item in the dropdown */
|
|
94
130
|
separator?: boolean;
|
|
131
|
+
/** Tier 2: all row actions require auth (role-gated at layout level) */
|
|
132
|
+
requiresAuth: true;
|
|
133
|
+
/** Minimum role required — enforced by RoleGate/ProtectedRoute at layout, NOT useAuthGate */
|
|
134
|
+
requiredRole?: "admin" | "moderator" | "seller" | "user";
|
|
135
|
+
/** RBAC permission key — shown in admin panel next to the toggle */
|
|
136
|
+
requiredPermission?: string;
|
|
137
|
+
/** Whether this row action is enabled by default (admin can toggle via actionConfig) */
|
|
138
|
+
defaultEnabled?: boolean;
|
|
95
139
|
}
|
|
96
140
|
export declare const ROW_ACTION_META: Record<RowActionId, RowActionMeta>;
|
|
97
141
|
export declare const ADMIN_ROW_ACTIONS: {
|
|
@@ -143,21 +187,11 @@ export interface FormActionMeta {
|
|
|
143
187
|
destructive?: boolean;
|
|
144
188
|
}
|
|
145
189
|
export declare const FORM_ACTION_META: Record<FormActionId, FormActionMeta>;
|
|
146
|
-
/**
|
|
147
|
-
* Preset ordered action groups for common form footer layouts.
|
|
148
|
-
* Consumed by FormShell and DrawerFormFooter to determine which buttons render,
|
|
149
|
-
* in which order (left → right).
|
|
150
|
-
*/
|
|
151
190
|
export declare const FORM_FOOTER_PRESET: {
|
|
152
|
-
/** Standard edit drawer: Cancel | Save Changes */
|
|
153
191
|
readonly drawerEdit: readonly ["form-cancel", "form-submit"];
|
|
154
|
-
/** Edit drawer with delete: Delete | Cancel | Save Changes */
|
|
155
192
|
readonly drawerEditDelete: readonly ["form-delete", "form-cancel", "form-submit"];
|
|
156
|
-
/** Content editor (blog / product): Discard | Save Draft | Publish */
|
|
157
193
|
readonly contentEditor: readonly ["form-discard", "form-save-draft", "form-publish"];
|
|
158
|
-
/** Simple modal / dialog: Cancel | Submit */
|
|
159
194
|
readonly modalForm: readonly ["form-cancel", "form-submit"];
|
|
160
|
-
/** Settings page: Reset | Save Changes */
|
|
161
195
|
readonly settingsForm: readonly ["form-reset", "form-submit"];
|
|
162
196
|
};
|
|
163
197
|
export declare const DASHBOARD_QUICK_ACTION_ID: {
|
|
@@ -187,6 +221,14 @@ export interface DashboardQuickActionMeta {
|
|
|
187
221
|
iconName?: string;
|
|
188
222
|
/** ROUTES key path that the button navigates to — consuming component resolves */
|
|
189
223
|
routeKey?: string;
|
|
224
|
+
/** All dashboard quick actions require auth (role-gated at layout level) */
|
|
225
|
+
requiresAuth: true;
|
|
226
|
+
/** Minimum role required — enforced by RoleGate/ProtectedRoute at layout */
|
|
227
|
+
requiredRole?: "admin" | "moderator" | "seller" | "user";
|
|
228
|
+
/** RBAC permission key — shown in admin panel next to the toggle */
|
|
229
|
+
requiredPermission?: string;
|
|
230
|
+
/** Whether this quick action is enabled by default */
|
|
231
|
+
defaultEnabled?: boolean;
|
|
190
232
|
}
|
|
191
233
|
export declare const DASHBOARD_QUICK_ACTION_META: Record<DashboardQuickActionId, DashboardQuickActionMeta>;
|
|
192
234
|
export declare const DASHBOARD_QUICK_ACTIONS: {
|