@mohasinac/appkit 2.7.50 → 2.7.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.js +65 -0
- package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.js +163 -0
- package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.js +115 -0
- package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.js +96 -0
- package/dist/client.d.ts +29 -6
- package/dist/client.js +16 -4
- package/dist/constants/api-endpoints.d.ts +42 -0
- package/dist/constants/api-endpoints.js +14 -0
- package/dist/features/_guide-cls.d.ts +13 -0
- package/dist/features/_guide-cls.js +14 -0
- package/dist/features/about/components/HelpPageView.js +29 -26
- package/dist/features/account/components/BuyerAccountGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerAccountGuideView.js +39 -0
- package/dist/features/account/components/BuyerAuctionsGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerAuctionsGuideView.js +38 -0
- package/dist/features/account/components/BuyerOrdersGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerOrdersGuideView.js +48 -0
- package/dist/features/account/components/BuyerShoppingGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerShoppingGuideView.js +38 -0
- package/dist/features/admin/components/AdminAnalyticsGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminAnalyticsGuideView.js +24 -0
- package/dist/features/admin/components/AdminBlogEditorView.js +100 -77
- package/dist/features/admin/components/AdminBundleEditorView.js +20 -14
- package/dist/features/admin/components/AdminBundlesView.d.ts +1 -3
- package/dist/features/admin/components/AdminBundlesView.js +173 -52
- package/dist/features/admin/components/AdminCatalogGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminCatalogGuideView.js +28 -0
- package/dist/features/admin/components/AdminContentGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminContentGuideView.js +36 -0
- package/dist/features/admin/components/AdminCouponEditorView.js +1 -1
- package/dist/features/admin/components/AdminGuideHubView.d.ts +5 -0
- package/dist/features/admin/components/AdminGuideHubView.js +79 -0
- package/dist/features/admin/components/AdminOrdersGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminOrdersGuideView.js +44 -0
- package/dist/features/admin/components/AdminProductsView.js +8 -3
- package/dist/features/admin/components/AdminSiteConfigGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminSiteConfigGuideView.js +21 -0
- package/dist/features/admin/components/AdminStoresGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminStoresGuideView.js +32 -0
- package/dist/features/admin/components/AdminTeamGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminTeamGuideView.js +33 -0
- package/dist/features/admin/components/AdminTrustGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminTrustGuideView.js +40 -0
- package/dist/features/admin/components/AdminUsersGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminUsersGuideView.js +38 -0
- package/dist/features/categories/components/CategoryBundlesListing.js +3 -33
- package/dist/features/categories/components/index.d.ts +0 -2
- package/dist/features/categories/components/index.js +0 -1
- package/dist/features/events/components/AdminEventEditorView.js +223 -269
- package/dist/features/grouped/repository/grouped-listings.repository.d.ts +9 -0
- package/dist/features/grouped/repository/grouped-listings.repository.js +12 -0
- package/dist/features/media/upload/MediaUploadField.js +1 -1
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +3 -1
- package/dist/features/products/components/AuctionsIndexListing.js +3 -1
- package/dist/features/products/components/MarketplaceBundleCard.d.ts +21 -0
- package/dist/features/products/components/MarketplaceBundleCard.js +56 -0
- package/dist/features/products/components/ProductsIndexListing.js +3 -1
- package/dist/features/products/components/index.d.ts +2 -4
- package/dist/features/products/components/index.js +1 -2
- package/dist/features/promotions/schemas/firestore.js +1 -1
- package/dist/features/reviews/components/index.d.ts +0 -2
- package/dist/features/reviews/components/index.js +0 -1
- package/dist/features/seller/components/BrandInlineSelect.js +23 -3
- package/dist/features/seller/components/CategoryInlineSelect.js +20 -3
- package/dist/features/seller/components/SellerAnalyticsAlertsView.d.ts +6 -0
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +124 -0
- package/dist/features/seller/components/SellerAnalyticsView.d.ts +2 -0
- package/dist/features/seller/components/SellerAnalyticsView.js +17 -9
- package/dist/features/seller/components/SellerBundlesView.d.ts +5 -0
- package/dist/features/seller/components/SellerBundlesView.js +104 -0
- package/dist/features/seller/components/SellerClassifiedView.d.ts +5 -0
- package/dist/features/seller/components/SellerClassifiedView.js +113 -0
- package/dist/features/seller/components/SellerDashboardView.js +19 -7
- package/dist/features/seller/components/SellerDigitalCodesView.d.ts +5 -0
- package/dist/features/seller/components/SellerDigitalCodesView.js +115 -0
- package/dist/features/seller/components/SellerGoogleReviewsView.d.ts +14 -0
- package/dist/features/seller/components/SellerGoogleReviewsView.js +95 -0
- package/dist/features/seller/components/SellerGroupedListingsView.d.ts +6 -0
- package/dist/features/seller/components/SellerGroupedListingsView.js +112 -0
- package/dist/features/seller/components/SellerLiveView.d.ts +5 -0
- package/dist/features/seller/components/SellerLiveView.js +113 -0
- package/dist/features/seller/components/SellerOrdersView.js +18 -2
- package/dist/features/seller/components/SellerPayoutMethodsView.d.ts +7 -0
- package/dist/features/seller/components/SellerPayoutMethodsView.js +120 -0
- package/dist/features/seller/components/SellerPayoutSettingsView.js +50 -12
- package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
- package/dist/features/seller/components/SellerProductShell.js +9 -7
- package/dist/features/seller/components/SellerProductsFilterDrawer.js +3 -1
- package/dist/features/seller/components/SellerProductsView.js +48 -54
- package/dist/features/seller/components/SellerShippingConfigsView.d.ts +7 -0
- package/dist/features/seller/components/SellerShippingConfigsView.js +149 -0
- package/dist/features/seller/components/SellerShippingView.js +37 -10
- package/dist/features/seller/components/SellerStoreCategoriesView.d.ts +9 -0
- package/dist/features/seller/components/SellerStoreCategoriesView.js +122 -0
- package/dist/features/seller/components/SellerStorefrontView.d.ts +0 -7
- package/dist/features/seller/components/SellerStorefrontView.js +26 -29
- package/dist/features/seller/components/SellerTemplatesView.d.ts +10 -0
- package/dist/features/seller/components/SellerTemplatesView.js +265 -0
- package/dist/features/seller/components/index.d.ts +26 -0
- package/dist/features/seller/components/index.js +13 -0
- package/dist/features/stores/api/[storeSlug]/reviews/route.js +49 -7
- package/dist/features/stores/components/StoreCapabilitiesGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreCapabilitiesGuideView.js +101 -0
- package/dist/features/stores/components/StoreFinanceGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreFinanceGuideView.js +79 -0
- package/dist/features/stores/components/StoreGuideHubView.d.ts +5 -0
- package/dist/features/stores/components/StoreGuideHubView.js +89 -0
- package/dist/features/stores/components/StoreListingsGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreListingsGuideView.js +151 -0
- package/dist/features/stores/components/StoreOrdersGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreOrdersGuideView.js +122 -0
- package/dist/features/stores/components/StoreReviewsListing.js +100 -23
- package/dist/features/stores/components/StoreSettingsGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreSettingsGuideView.js +56 -0
- package/dist/features/stores/components/index.d.ts +12 -1
- package/dist/features/stores/components/index.js +6 -1
- package/dist/features/stores/hooks/useStores.d.ts +5 -0
- package/dist/features/stores/hooks/useStores.js +10 -0
- package/dist/features/stores/manifest.js +1 -1
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +13 -2
- package/dist/index.d.ts +54 -9
- package/dist/index.js +58 -15
- package/dist/next/routing/route-map.d.ts +55 -0
- package/dist/next/routing/route-map.js +25 -0
- package/dist/providers/db-firebase/__tests__/filter-aliases.test.d.ts +1 -0
- package/dist/providers/db-firebase/__tests__/filter-aliases.test.js +93 -0
- package/dist/providers/db-firebase/sieve.d.ts +49 -0
- package/dist/providers/db-firebase/sieve.js +61 -8
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.js +2 -0
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/ListingToolbar.d.ts +8 -1
- package/dist/ui/components/ListingToolbar.js +4 -2
- package/dist/ui/components/__tests__/BulkActionBar.test.d.ts +1 -0
- package/dist/ui/components/__tests__/BulkActionBar.test.js +96 -0
- package/dist/ui/components/__tests__/ListingToolbar.test.d.ts +1 -0
- package/dist/ui/components/__tests__/ListingToolbar.test.js +125 -0
- package/dist/ui/index.d.ts +1 -1
- package/package.json +8 -2
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseRepository } from "../../../providers/db-firebase";
|
|
2
|
+
import { type GroupedListingDocument } from "../schemas/firestore";
|
|
3
|
+
export declare class GroupedListingsRepository extends BaseRepository<GroupedListingDocument> {
|
|
4
|
+
constructor();
|
|
5
|
+
listByStore(storeId: string): Promise<{
|
|
6
|
+
items: GroupedListingDocument[];
|
|
7
|
+
}>;
|
|
8
|
+
}
|
|
9
|
+
export declare const groupedListingsRepository: GroupedListingsRepository;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseRepository } from "../../../providers/db-firebase";
|
|
2
|
+
import { GROUPED_LISTINGS_COLLECTION, } from "../schemas/firestore";
|
|
3
|
+
export class GroupedListingsRepository extends BaseRepository {
|
|
4
|
+
constructor() {
|
|
5
|
+
super(GROUPED_LISTINGS_COLLECTION);
|
|
6
|
+
}
|
|
7
|
+
async listByStore(storeId) {
|
|
8
|
+
const items = await this.findBy("storeId", storeId);
|
|
9
|
+
return { items };
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export const groupedListingsRepository = new GroupedListingsRepository();
|
|
@@ -78,7 +78,7 @@ export function MediaUploadField({ label, value, onChange, onChangeField, onUplo
|
|
|
78
78
|
// SB-UNI-Z5 2026-05-13 — `kind` derives accept/maxSizeMB when not set
|
|
79
79
|
// explicitly; explicit props still win. "*" + 50 stay as the floor for
|
|
80
80
|
// back-compat with callers that pass neither.
|
|
81
|
-
kind, accept: acceptProp, maxSizeMB: maxSizeMBProp, disabled = false, helperText, captureSource = "both", captureMode = "photo", enableTrim = true, enableThumbnail = true, onThumbnailChange, showYoutube =
|
|
81
|
+
kind, accept: acceptProp, maxSizeMB: maxSizeMBProp, disabled = false, helperText, captureSource = "both", captureMode = "photo", enableTrim = true, enableThumbnail = true, onThumbnailChange, showYoutube = true, showExternal = true, onAbort, onStagedUrlsChange, isPersisted = false, }) {
|
|
82
82
|
const [isLoading, setIsLoading] = useState(false);
|
|
83
83
|
const [error, setError] = useState(null);
|
|
84
84
|
const [pendingVideoUrl, setPendingVideoUrl] = useState(null);
|
|
@@ -129,7 +129,9 @@ export function PreOrdersIndexListing({ initialData, categorySlug, brandName })
|
|
|
129
129
|
}, [localCart, showToast]);
|
|
130
130
|
const selection = useBulkSelection({ items: preOrders, keyExtractor: (p) => p.id });
|
|
131
131
|
const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4";
|
|
132
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search pre-orders...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: PREORDER_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: preOrders.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection,
|
|
132
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search pre-orders...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: PREORDER_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: preOrders.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection, toggles: [
|
|
133
|
+
{ label: "Show closed", active: showClosed, onChange: (next) => table.set(TABLE_KEYS.SHOW_CLOSED, next ? "true" : "") },
|
|
134
|
+
] }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
|
|
133
135
|
{
|
|
134
136
|
id: ACTION_ID.ADD_TO_CART,
|
|
135
137
|
label: ACTION_META[ACTION_ID.ADD_TO_CART].label,
|
|
@@ -111,7 +111,9 @@ export function AuctionsIndexListing({ initialData, categorySlug, brandName }) {
|
|
|
111
111
|
isWishlisted: (productId) => wishlistedIds.has(productId),
|
|
112
112
|
};
|
|
113
113
|
const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-3 gap-4";
|
|
114
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search auctions...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: AUCTION_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: auctions.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection,
|
|
114
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search auctions...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: AUCTION_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: auctions.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection, toggles: [
|
|
115
|
+
{ label: "Show ended", active: showEnded, onChange: (next) => table.set(TABLE_KEYS.SHOW_ENDED, next ? "true" : "") },
|
|
116
|
+
] }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
|
|
115
117
|
{
|
|
116
118
|
id: ACTION_ID.WATCH_AUCTION,
|
|
117
119
|
label: "Add to Watchlist",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CategoryDocument } from "../../categories/schemas/firestore";
|
|
2
|
+
export type MarketplaceBundleCardData = Pick<CategoryDocument, "id" | "name" | "slug" | "bundleItemDetails" | "bundleProductIds" | "bundlePriceInPaise" | "bundleStockStatus" | "display">;
|
|
3
|
+
export interface MarketplaceBundleCardLabels {
|
|
4
|
+
bundleBadge?: string;
|
|
5
|
+
inStockBadge?: string;
|
|
6
|
+
outOfStockBadge?: string;
|
|
7
|
+
itemsLabel?: (count: number) => string;
|
|
8
|
+
}
|
|
9
|
+
export interface MarketplaceBundleCardProps {
|
|
10
|
+
bundle: MarketplaceBundleCardData;
|
|
11
|
+
className?: string;
|
|
12
|
+
variant?: "grid" | "list";
|
|
13
|
+
selectable?: boolean;
|
|
14
|
+
isSelected?: boolean;
|
|
15
|
+
onSelect?: (id: string, selected: boolean) => void;
|
|
16
|
+
href?: string;
|
|
17
|
+
hrefBuilder?: (bundle: MarketplaceBundleCardData) => string;
|
|
18
|
+
onNavigate?: (href: string) => void;
|
|
19
|
+
labels?: MarketplaceBundleCardLabels;
|
|
20
|
+
}
|
|
21
|
+
export declare function MarketplaceBundleCard({ bundle, className, variant, selectable, isSelected, onSelect, href, hrefBuilder, onNavigate, labels, }: MarketplaceBundleCardProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { useLongPress } from "../../../react/hooks/useLongPress";
|
|
6
|
+
import { ROUTES } from "../../../next";
|
|
7
|
+
import { formatCurrency } from "../../../utils";
|
|
8
|
+
import { getDefaultCurrency } from "../../../core/baseline-resolver";
|
|
9
|
+
import { BaseListingCard, Div, Row, Span, Text, TextLink, } from "../../../ui";
|
|
10
|
+
import { THEME_CONSTANTS } from "../../../tokens";
|
|
11
|
+
const DEFAULT_LABELS = {
|
|
12
|
+
bundleBadge: "Bundle",
|
|
13
|
+
inStockBadge: "In stock",
|
|
14
|
+
outOfStockBadge: "Out of stock",
|
|
15
|
+
itemsLabel: (count) => `${count} item${count !== 1 ? "s" : ""}`,
|
|
16
|
+
};
|
|
17
|
+
function resolveHref(bundle, href, hrefBuilder) {
|
|
18
|
+
if (href)
|
|
19
|
+
return href;
|
|
20
|
+
if (hrefBuilder)
|
|
21
|
+
return hrefBuilder(bundle);
|
|
22
|
+
return ROUTES.PUBLIC.BUNDLE_DETAIL(bundle.slug);
|
|
23
|
+
}
|
|
24
|
+
export function MarketplaceBundleCard({ bundle, className = "", variant = "grid", selectable = false, isSelected = false, onSelect, href, hrefBuilder, onNavigate, labels, }) {
|
|
25
|
+
const router = useRouter();
|
|
26
|
+
const mergedLabels = { ...DEFAULT_LABELS, ...labels };
|
|
27
|
+
const detailHref = resolveHref(bundle, href, hrefBuilder);
|
|
28
|
+
const longPress = useLongPress(() => onSelect?.(bundle.id, !isSelected));
|
|
29
|
+
const memberCount = bundle.bundleProductIds?.length ?? 0;
|
|
30
|
+
const stock = bundle.bundleStockStatus ?? "in_stock";
|
|
31
|
+
const cover = bundle.display?.coverImage;
|
|
32
|
+
const price = bundle.bundlePriceInPaise;
|
|
33
|
+
const collageTiles = (bundle.bundleItemDetails ?? [])
|
|
34
|
+
.filter((d) => Boolean(d.imageURL))
|
|
35
|
+
.slice(0, 4);
|
|
36
|
+
const showCollage = collageTiles.length >= 2;
|
|
37
|
+
const overflow = memberCount - collageTiles.length;
|
|
38
|
+
const handleNavigate = useCallback(() => {
|
|
39
|
+
if (onNavigate) {
|
|
40
|
+
onNavigate(String(detailHref));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
router.push(String(detailHref));
|
|
44
|
+
}, [detailHref, onNavigate, router]);
|
|
45
|
+
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: String(detailHref), className: "absolute inset-0 block", children: showCollage ? (_jsx(Div, { className: `grid h-full w-full gap-0.5 bg-[var(--appkit-color-surface-muted)] ${collageTiles.length === 2 ? "grid-cols-2 grid-rows-1" : "grid-cols-2 grid-rows-2"}`, children: collageTiles.map((tile, i) => (_jsxs(Div, { className: "relative overflow-hidden bg-[var(--appkit-color-surface-muted)]", children: [_jsx("img", { src: tile.imageURL, alt: tile.title ?? `${bundle.name} item ${i + 1}`, loading: "lazy", className: "absolute inset-0 h-full w-full object-cover" }), i === collageTiles.length - 1 && overflow > 0 && (_jsxs(Div, { className: "absolute inset-0 flex items-center justify-center bg-black/55 text-sm font-semibold text-white", children: ["+", overflow] }))] }, `${tile.productId}-${i}`))) })) : cover ? (_jsx(Div, { className: "absolute inset-0 bg-[var(--appkit-color-surface-muted)]", children: _jsx("img", { src: cover, alt: bundle.name, loading: "lazy", className: "h-full w-full object-cover" }) })) : (_jsx(Div, { className: "absolute inset-0 flex items-center justify-center bg-[var(--appkit-color-surface-muted)]", children: _jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)]", children: "No 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-violet-600 px-2 py-0.5 text-xs font-medium text-white", children: mergedLabels.bundleBadge }), _jsx(Span, { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${stock === "out_of_stock"
|
|
46
|
+
? "bg-zinc-500 text-white"
|
|
47
|
+
: "bg-emerald-600 text-white"}`, children: stock === "out_of_stock" ? mergedLabels.outOfStockBadge : mergedLabels.inStockBadge })] }), stock === "out_of_stock" && (_jsx(Div, { className: "absolute inset-0 flex items-center justify-center bg-black/40", children: _jsx(Span, { className: "rounded bg-zinc-900/80 px-3 py-1 text-xs font-bold uppercase tracking-wider text-white", children: mergedLabels.outOfStockBadge }) })), onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (event) => {
|
|
48
|
+
event.stopPropagation();
|
|
49
|
+
onSelect(bundle.id, !isSelected);
|
|
50
|
+
}, className: selectable || isSelected
|
|
51
|
+
? "opacity-100"
|
|
52
|
+
: "opacity-0 group-hover:opacity-100 transition-opacity" }))] }), _jsxs(BaseListingCard.Info, { variant: variant, children: [_jsx(TextLink, { href: String(detailHref), children: _jsx(Text, { className: `${THEME_CONSTANTS.utilities.textClamp2} text-sm font-medium text-zinc-900 dark:text-zinc-100`, children: bundle.name }) }), _jsxs(Row, { justify: "between", className: "mt-1 gap-2", children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: price
|
|
53
|
+
? formatCurrency(price / 100, getDefaultCurrency())
|
|
54
|
+
: "—" }), _jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)]", children: mergedLabels.itemsLabel(memberCount) })] }), _jsx("div", { className: "mt-2 w-full cursor-pointer rounded-md bg-violet-600 py-1.5 text-center text-xs font-semibold text-white transition-colors hover:bg-violet-700 active:scale-[0.98]", role: "button", tabIndex: 0, onClick: handleNavigate, onKeyDown: (e) => { if (e.key === "Enter" || e.key === " ")
|
|
55
|
+
handleNavigate(); }, children: "View Bundle" })] })] }));
|
|
56
|
+
}
|
|
@@ -132,7 +132,9 @@ export function ProductsIndexListing({ initialData }) {
|
|
|
132
132
|
router.push(String(ROUTES.USER.CART));
|
|
133
133
|
});
|
|
134
134
|
}, [localCart, router, requireAuth]);
|
|
135
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: PRODUCT_PUBLIC_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: products.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection,
|
|
135
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: PRODUCT_PUBLIC_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: products.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection, toggles: [
|
|
136
|
+
{ label: "Show sold", active: showSold, onChange: (next) => table.set(TABLE_KEYS.SHOW_SOLD, next ? "true" : "") },
|
|
137
|
+
] }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
|
|
136
138
|
{
|
|
137
139
|
id: ACTION_ID.ADD_TO_CART,
|
|
138
140
|
label: ACTION_META[ACTION_ID.ADD_TO_CART].label,
|
|
@@ -15,6 +15,8 @@ export { PrizeRevealModal } from "./PrizeRevealModal";
|
|
|
15
15
|
export type { PrizeRevealModalProps, PrizeRevealResponse, } from "./PrizeRevealModal";
|
|
16
16
|
export { MarketplacePrizeDrawCard } from "./MarketplacePrizeDrawCard";
|
|
17
17
|
export type { MarketplacePrizeDrawCardProps, MarketplacePrizeDrawCardData, MarketplacePrizeDrawCardLabels, } from "./MarketplacePrizeDrawCard";
|
|
18
|
+
export { MarketplaceBundleCard } from "./MarketplaceBundleCard";
|
|
19
|
+
export type { MarketplaceBundleCardProps, MarketplaceBundleCardData, MarketplaceBundleCardLabels, } from "./MarketplaceBundleCard";
|
|
18
20
|
export { PrizeDrawsIndexListing } from "./PrizeDrawsIndexListing";
|
|
19
21
|
export type { PrizeDrawsIndexListingProps } from "./PrizeDrawsIndexListing";
|
|
20
22
|
export { PrizeDrawEntryActions } from "./PrizeDrawEntryActions";
|
|
@@ -30,14 +32,10 @@ export { BuyBar } from "./BuyBar";
|
|
|
30
32
|
export type { BuyBarProps } from "./BuyBar";
|
|
31
33
|
export { ProductsView } from "./ProductsView";
|
|
32
34
|
export type { ProductsViewProps } from "./ProductsView";
|
|
33
|
-
export { AuctionsView } from "./AuctionsView";
|
|
34
|
-
export type { AuctionsViewProps } from "./AuctionsView";
|
|
35
35
|
export { ProductDetailView } from "./ProductDetailView";
|
|
36
36
|
export type { ProductDetailViewProps } from "./ProductDetailView";
|
|
37
37
|
export { AuctionDetailView } from "./AuctionDetailView";
|
|
38
38
|
export type { AuctionDetailViewProps } from "./AuctionDetailView";
|
|
39
|
-
export { PreOrdersView } from "./PreOrdersView";
|
|
40
|
-
export type { PreOrdersViewProps } from "./PreOrdersView";
|
|
41
39
|
export { PreOrderDetailView } from "./PreOrderDetailView";
|
|
42
40
|
export type { PreOrderDetailViewProps } from "./PreOrderDetailView";
|
|
43
41
|
export { ProductInfo } from "./ProductInfo";
|
|
@@ -7,6 +7,7 @@ export { PrizeDrawItemsEditor } from "./PrizeDrawItemsEditor";
|
|
|
7
7
|
export { PrizeDrawCollage } from "./PrizeDrawCollage";
|
|
8
8
|
export { PrizeRevealModal } from "./PrizeRevealModal";
|
|
9
9
|
export { MarketplacePrizeDrawCard } from "./MarketplacePrizeDrawCard";
|
|
10
|
+
export { MarketplaceBundleCard } from "./MarketplaceBundleCard";
|
|
10
11
|
export { PrizeDrawsIndexListing } from "./PrizeDrawsIndexListing";
|
|
11
12
|
// PrizeDrawsListingView + PrizeDrawDetailPageView are server components that
|
|
12
13
|
// import productRepository (→ firebase-admin). Re-exporting them from this
|
|
@@ -20,10 +21,8 @@ export { FeatureBadge, FeatureBadgeList } from "./FeatureBadge";
|
|
|
20
21
|
export { ProductFeaturesProvider, useProductFeatures, } from "./ProductFeaturesContext";
|
|
21
22
|
export { BuyBar } from "./BuyBar";
|
|
22
23
|
export { ProductsView } from "./ProductsView";
|
|
23
|
-
export { AuctionsView } from "./AuctionsView";
|
|
24
24
|
export { ProductDetailView } from "./ProductDetailView";
|
|
25
25
|
export { AuctionDetailView } from "./AuctionDetailView";
|
|
26
|
-
export { PreOrdersView } from "./PreOrdersView";
|
|
27
26
|
export { PreOrderDetailView } from "./PreOrderDetailView";
|
|
28
27
|
export { ProductInfo } from "./ProductInfo";
|
|
29
28
|
export { ProductTabs } from "./ProductTabs";
|
|
@@ -61,7 +61,7 @@ export const COUPON_TYPE_LABELS = {
|
|
|
61
61
|
};
|
|
62
62
|
export const DEFAULT_COUPON_DATA = {
|
|
63
63
|
usage: { currentUsage: 0 },
|
|
64
|
-
validity: { isActive:
|
|
64
|
+
validity: { isActive: true, startDate: new Date(), endDate: undefined },
|
|
65
65
|
restrictions: { firstTimeUserOnly: false, combineWithSellerCoupons: false },
|
|
66
66
|
stats: { totalUses: 0, totalRevenue: 0, totalDiscount: 0 },
|
|
67
67
|
};
|
|
@@ -6,8 +6,6 @@ export type { ReviewSummaryProps } from "./ReviewSummary";
|
|
|
6
6
|
export { ReviewSummary } from "./ReviewSummary";
|
|
7
7
|
export type { ViewReviewModalProps } from "./ReviewModal";
|
|
8
8
|
export { ViewReviewModal } from "./ReviewModal";
|
|
9
|
-
export { ReviewsListView } from "./ReviewsListView";
|
|
10
|
-
export type { ReviewsListViewProps } from "./ReviewsListView";
|
|
11
9
|
export { ReviewsIndexListing } from "./ReviewsIndexListing";
|
|
12
10
|
export type { ReviewsIndexListingProps } from "./ReviewsIndexListing";
|
|
13
11
|
export type { ReviewDetailPageViewProps } from "./ReviewDetailPageView";
|
|
@@ -2,5 +2,4 @@ export { ReviewCard, ReviewsList } from "./ReviewsList";
|
|
|
2
2
|
export { ReviewFilters, REVIEW_SORT_OPTIONS, REVIEW_FILTER_KEYS, REVIEW_ADMIN_SORT_OPTIONS, REVIEW_SELLER_SORT_OPTIONS, REVIEW_PUBLIC_SORT_OPTIONS, getReviewFilterKeys, getReviewSortOptions, } from "./ReviewFilters";
|
|
3
3
|
export { ReviewSummary } from "./ReviewSummary";
|
|
4
4
|
export { ViewReviewModal } from "./ReviewModal";
|
|
5
|
-
export { ReviewsListView } from "./ReviewsListView";
|
|
6
5
|
export { ReviewsIndexListing } from "./ReviewsIndexListing";
|
|
@@ -4,14 +4,16 @@ import { InlineCreateSelect } from "../../../ui/components/InlineCreateSelect";
|
|
|
4
4
|
import { BrandQuickCreateForm } from "../../admin/components/BrandQuickCreateForm";
|
|
5
5
|
import { apiClient } from "../../../http";
|
|
6
6
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
7
|
-
|
|
7
|
+
// Admin: paginated search via /api/admin/brands (admin/mod only).
|
|
8
|
+
// Response via successResponse({ data: items, total }) → apiClient unwraps to { data, total }.
|
|
9
|
+
async function loadAdminBrandOptions(query, page) {
|
|
8
10
|
const params = new URLSearchParams({
|
|
9
11
|
q: query,
|
|
10
12
|
page: String(page),
|
|
11
13
|
pageSize: "20",
|
|
12
14
|
});
|
|
13
15
|
const res = await apiClient.get(`${ADMIN_ENDPOINTS.BRANDS}?${params}`);
|
|
14
|
-
const raw = res.
|
|
16
|
+
const raw = res.data ?? res.items ?? [];
|
|
15
17
|
const items = raw.map((b) => ({
|
|
16
18
|
value: String(b.id ?? ""),
|
|
17
19
|
label: String(b.name ?? ""),
|
|
@@ -19,8 +21,26 @@ async function loadBrandOptions(query, page) {
|
|
|
19
21
|
const total = res.total ?? items.length;
|
|
20
22
|
return { items, hasMore: page * 20 < total, nextPage: page + 1 };
|
|
21
23
|
}
|
|
24
|
+
// Seller/public: unauthenticated /api/brands endpoint returns { items, total }.
|
|
25
|
+
// Client-side query filtering so all sellers can access the brand list.
|
|
26
|
+
async function loadPublicBrandOptions(query, page) {
|
|
27
|
+
const params = new URLSearchParams({
|
|
28
|
+
page: String(page),
|
|
29
|
+
pageSize: "100",
|
|
30
|
+
active: "true",
|
|
31
|
+
});
|
|
32
|
+
const res = await apiClient.get(`/api/brands?${params}`);
|
|
33
|
+
const all = (res.items ?? []).map((b) => ({
|
|
34
|
+
value: String(b.id ?? ""),
|
|
35
|
+
label: String(b.name ?? ""),
|
|
36
|
+
}));
|
|
37
|
+
const q = query.trim().toLowerCase();
|
|
38
|
+
const items = q ? all.filter((b) => b.label.toLowerCase().includes(q)) : all;
|
|
39
|
+
return { items, hasMore: false, nextPage: page + 1 };
|
|
40
|
+
}
|
|
22
41
|
export function BrandInlineSelect({ value, onChange, placeholder = "Search brands…", disabled, allowCreate = true, }) {
|
|
23
|
-
|
|
42
|
+
const loadOptions = allowCreate ? loadAdminBrandOptions : loadPublicBrandOptions;
|
|
43
|
+
return (_jsx(InlineCreateSelect, { value: value || null, onChange: (v) => onChange(v ?? ""), loadOptions: loadOptions, placeholder: placeholder, disabled: disabled, createLabel: "brand", drawerTitle: "Create Brand", renderCreateForm: allowCreate
|
|
24
44
|
? ({ onCreated, onCancel }) => (_jsx(BrandQuickCreateForm, { onSaved: (id, name) => onCreated({ value: id, label: name }), onCancel: onCancel }))
|
|
25
45
|
: undefined }));
|
|
26
46
|
}
|
|
@@ -4,7 +4,9 @@ import { InlineCreateSelect } from "../../../ui/components/InlineCreateSelect";
|
|
|
4
4
|
import { CategoryQuickCreateForm } from "../../admin/components/CategoryQuickCreateForm";
|
|
5
5
|
import { apiClient } from "../../../http";
|
|
6
6
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
7
|
-
|
|
7
|
+
// Admin: paginated + server-side search via /api/admin/categories (admin/mod only).
|
|
8
|
+
// Response via successResponse({ data: items, total }) → apiClient unwraps to { data, total, ... }.
|
|
9
|
+
async function loadAdminCategoryOptions(query, page) {
|
|
8
10
|
const params = new URLSearchParams({
|
|
9
11
|
q: query,
|
|
10
12
|
page: String(page),
|
|
@@ -12,15 +14,30 @@ async function loadCategoryOptions(query, page) {
|
|
|
12
14
|
flat: "true",
|
|
13
15
|
});
|
|
14
16
|
const res = await apiClient.get(`${ADMIN_ENDPOINTS.CATEGORIES}?${params}`);
|
|
15
|
-
const items = (res.
|
|
17
|
+
const items = (res.data ?? []).map((c) => ({
|
|
16
18
|
value: String(c.id ?? ""),
|
|
17
19
|
label: String(c.name ?? ""),
|
|
18
20
|
}));
|
|
19
21
|
const total = res.total ?? items.length;
|
|
20
22
|
return { items, hasMore: page * 20 < total, nextPage: page + 1 };
|
|
21
23
|
}
|
|
24
|
+
// Seller: fetch all flat categories from the public unauthenticated endpoint,
|
|
25
|
+
// filter client-side by query. Public endpoint returns { success, data: CategoryItem[] }.
|
|
26
|
+
async function loadPublicCategoryOptions(query, _page) {
|
|
27
|
+
const params = new URLSearchParams({ flat: "true", pageSize: "200" });
|
|
28
|
+
const res = await fetch(`/api/categories?${params}`, { credentials: "include" });
|
|
29
|
+
const json = await res.json().catch(() => ({}));
|
|
30
|
+
const all = (json.data ?? []).map((c) => ({
|
|
31
|
+
value: String(c.id ?? ""),
|
|
32
|
+
label: String(c.name ?? ""),
|
|
33
|
+
}));
|
|
34
|
+
const q = query.trim().toLowerCase();
|
|
35
|
+
const items = q ? all.filter((c) => c.label.toLowerCase().includes(q)) : all;
|
|
36
|
+
return { items, hasMore: false, nextPage: 2 };
|
|
37
|
+
}
|
|
22
38
|
export function CategoryInlineSelect({ value, onChange, placeholder = "Search categories…", disabled, allowCreate = false, }) {
|
|
23
|
-
|
|
39
|
+
const loadOptions = allowCreate ? loadAdminCategoryOptions : loadPublicCategoryOptions;
|
|
40
|
+
return (_jsx(InlineCreateSelect, { value: value || null, onChange: (v) => onChange(v ?? ""), loadOptions: loadOptions, placeholder: placeholder, disabled: disabled, createLabel: "category", drawerTitle: "Create Category", renderCreateForm: allowCreate
|
|
24
41
|
? ({ onCreated, onCancel }) => (_jsx(CategoryQuickCreateForm, { onSaved: (id, name) => onCreated({ value: id, label: name }), onCancel: onCancel }))
|
|
25
42
|
: undefined }));
|
|
26
43
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback } from "react";
|
|
4
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
5
|
+
import { Alert, Badge, Button, Div, Form, FormActions, Heading, Input, Row, Section, Select, Text, Toggle, useToast, } from "../../../ui";
|
|
6
|
+
import { apiClient } from "../../../http";
|
|
7
|
+
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Config
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
const OPERATOR_OPTIONS = [
|
|
12
|
+
{ value: ">", label: "Greater than (>)" },
|
|
13
|
+
{ value: "<", label: "Less than (<)" },
|
|
14
|
+
{ value: ">=", label: "≥" },
|
|
15
|
+
{ value: "<=", label: "≤" },
|
|
16
|
+
{ value: "==", label: "Equals (==)" },
|
|
17
|
+
];
|
|
18
|
+
const METRIC_OPTIONS = [
|
|
19
|
+
{ value: "daily_revenue", label: "Daily Revenue (₹)" },
|
|
20
|
+
{ value: "daily_orders", label: "Daily Orders" },
|
|
21
|
+
{ value: "low_stock", label: "Low Stock (products)" },
|
|
22
|
+
{ value: "cancelled_orders", label: "Cancelled Orders" },
|
|
23
|
+
{ value: "pending_payouts", label: "Pending Payouts (₹)" },
|
|
24
|
+
{ value: "avg_rating", label: "Average Rating" },
|
|
25
|
+
{ value: "reviews_count", label: "New Reviews" },
|
|
26
|
+
];
|
|
27
|
+
const WINDOW_OPTIONS = [
|
|
28
|
+
{ value: "1", label: "Last 1 hour" },
|
|
29
|
+
{ value: "6", label: "Last 6 hours" },
|
|
30
|
+
{ value: "24", label: "Last 24 hours" },
|
|
31
|
+
{ value: "168", label: "Last 7 days" },
|
|
32
|
+
];
|
|
33
|
+
const CHANNEL_OPTIONS = [
|
|
34
|
+
{ value: "in-app", label: "In-App notification" },
|
|
35
|
+
{ value: "email", label: "Email" },
|
|
36
|
+
{ value: "whatsapp", label: "WhatsApp" },
|
|
37
|
+
];
|
|
38
|
+
const OPERATOR_BADGE = {
|
|
39
|
+
">": "danger",
|
|
40
|
+
"<": "warning",
|
|
41
|
+
">=": "danger",
|
|
42
|
+
"<=": "warning",
|
|
43
|
+
"==": "info",
|
|
44
|
+
"!=": "default",
|
|
45
|
+
};
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Sub-components
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
function AlertCard({ alert, onToggle, onDelete, }) {
|
|
50
|
+
const metricLabel = METRIC_OPTIONS.find((m) => m.value === alert.metric)?.label ?? alert.metric;
|
|
51
|
+
const opLabel = OPERATOR_OPTIONS.find((o) => o.value === alert.operator)?.label ?? alert.operator;
|
|
52
|
+
const windowLabel = WINDOW_OPTIONS.find((w) => w.value === String(alert.windowHours))?.label ?? `${alert.windowHours}h`;
|
|
53
|
+
return (_jsxs(Div, { className: "flex items-start justify-between gap-4 p-4 rounded-lg border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-900", children: [_jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs(Row, { className: "items-center gap-2 mb-1 flex-wrap", children: [_jsx(Text, { className: "font-medium text-zinc-900 dark:text-zinc-100 text-sm", children: alert.label }), _jsx(Badge, { variant: alert.isActive ? "success" : "default", children: alert.isActive ? "Active" : "Paused" })] }), _jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 mb-1", children: [metricLabel, " ", opLabel, " ", alert.threshold, " \u00B7 ", windowLabel] }), _jsx(Row, { className: "gap-1 flex-wrap", children: alert.notifyChannels.map((ch) => (_jsx(Badge, { variant: OPERATOR_BADGE[alert.operator] ?? "default", children: ch }, ch))) }), alert.lastTriggeredAt && (_jsxs(Text, { className: "text-xs text-zinc-400 dark:text-zinc-500 mt-1", children: ["Last triggered: ", new Date(alert.lastTriggeredAt).toLocaleString("en-IN")] }))] }), _jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [_jsx(Toggle, { checked: alert.isActive, onChange: (v) => onToggle(alert.id, v), size: "sm" }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => onDelete(alert.id), className: "text-red-500 hover:text-red-600", children: "Delete" })] })] }));
|
|
54
|
+
}
|
|
55
|
+
const EMPTY_DRAFT = {
|
|
56
|
+
label: "",
|
|
57
|
+
metric: "daily_orders",
|
|
58
|
+
operator: ">",
|
|
59
|
+
threshold: "",
|
|
60
|
+
windowHours: "24",
|
|
61
|
+
notifyChannels: ["in-app"],
|
|
62
|
+
};
|
|
63
|
+
export function SellerAnalyticsAlertsView({ labels = {}, }) {
|
|
64
|
+
const queryClient = useQueryClient();
|
|
65
|
+
const { showToast } = useToast();
|
|
66
|
+
const [showForm, setShowForm] = useState(false);
|
|
67
|
+
const [draft, setDraft] = useState(EMPTY_DRAFT);
|
|
68
|
+
const { data, isLoading } = useQuery({
|
|
69
|
+
queryKey: ["seller", "analytics-alerts"],
|
|
70
|
+
queryFn: async () => {
|
|
71
|
+
const res = await apiClient.get(SELLER_ENDPOINTS.ANALYTICS_ALERTS);
|
|
72
|
+
return (res?.items ?? []);
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
const alerts = data ?? [];
|
|
76
|
+
const createMutation = useMutation({
|
|
77
|
+
mutationFn: async () => {
|
|
78
|
+
const payload = {
|
|
79
|
+
label: draft.label,
|
|
80
|
+
metric: draft.metric,
|
|
81
|
+
operator: draft.operator,
|
|
82
|
+
threshold: Number(draft.threshold),
|
|
83
|
+
windowHours: Number(draft.windowHours),
|
|
84
|
+
notifyChannels: draft.notifyChannels,
|
|
85
|
+
scope: "seller",
|
|
86
|
+
isActive: true,
|
|
87
|
+
};
|
|
88
|
+
return apiClient.post(SELLER_ENDPOINTS.ANALYTICS_ALERTS, payload);
|
|
89
|
+
},
|
|
90
|
+
onSuccess: () => {
|
|
91
|
+
showToast("Alert created", "success");
|
|
92
|
+
void queryClient.invalidateQueries({ queryKey: ["seller", "analytics-alerts"] });
|
|
93
|
+
setShowForm(false);
|
|
94
|
+
setDraft(EMPTY_DRAFT);
|
|
95
|
+
},
|
|
96
|
+
onError: () => showToast("Failed to create alert", "error"),
|
|
97
|
+
});
|
|
98
|
+
const toggleMutation = useMutation({
|
|
99
|
+
mutationFn: async ({ id, isActive }) => apiClient.patch(SELLER_ENDPOINTS.ANALYTICS_ALERT_BY_ID(id), { isActive }),
|
|
100
|
+
onSuccess: () => {
|
|
101
|
+
void queryClient.invalidateQueries({ queryKey: ["seller", "analytics-alerts"] });
|
|
102
|
+
},
|
|
103
|
+
onError: () => showToast("Failed to update alert", "error"),
|
|
104
|
+
});
|
|
105
|
+
const deleteMutation = useMutation({
|
|
106
|
+
mutationFn: async (id) => apiClient.delete(SELLER_ENDPOINTS.ANALYTICS_ALERT_BY_ID(id)),
|
|
107
|
+
onSuccess: () => {
|
|
108
|
+
showToast("Alert deleted", "success");
|
|
109
|
+
void queryClient.invalidateQueries({ queryKey: ["seller", "analytics-alerts"] });
|
|
110
|
+
},
|
|
111
|
+
onError: () => showToast("Failed to delete alert", "error"),
|
|
112
|
+
});
|
|
113
|
+
const toggleChannel = useCallback((ch) => {
|
|
114
|
+
setDraft((d) => ({
|
|
115
|
+
...d,
|
|
116
|
+
notifyChannels: d.notifyChannels.includes(ch)
|
|
117
|
+
? d.notifyChannels.filter((c) => c !== ch)
|
|
118
|
+
: [...d.notifyChannels, ch],
|
|
119
|
+
}));
|
|
120
|
+
}, []);
|
|
121
|
+
return (_jsxs("div", { className: "space-y-6", children: [_jsxs(Row, { className: "items-center justify-between", children: [_jsx(Heading, { level: 2, className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: labels.title ?? "Analytics Alerts" }), _jsx(Button, { size: "sm", onClick: () => setShowForm((v) => !v), children: showForm ? "Cancel" : "+ New Alert" })] }), _jsx(Text, { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "Get notified when a key metric crosses a threshold. Alerts check the metric over the selected window and fire through your chosen channels." }), showForm && (_jsxs(Section, { className: "border border-zinc-200 dark:border-zinc-700 rounded-xl p-5 space-y-4", children: [_jsx(Heading, { level: 3, className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Create Alert" }), _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); createMutation.mutate(); }, children: [_jsxs("div", { className: "grid sm:grid-cols-2 gap-4", children: [_jsx(Input, { label: "Alert label", value: draft.label, onChange: (e) => setDraft((d) => ({ ...d, label: e.target.value })), placeholder: "e.g. Low daily orders", required: true }), _jsx(Select, { label: "Metric", value: draft.metric, onValueChange: (v) => setDraft((d) => ({ ...d, metric: v })), options: METRIC_OPTIONS }), _jsx(Select, { label: "Operator", value: draft.operator, onValueChange: (v) => setDraft((d) => ({ ...d, operator: v })), options: OPERATOR_OPTIONS }), _jsx(Input, { label: "Threshold", type: "number", value: draft.threshold, onChange: (e) => setDraft((d) => ({ ...d, threshold: e.target.value })), placeholder: "e.g. 10", required: true }), _jsx(Select, { label: "Check window", value: draft.windowHours, onValueChange: (v) => setDraft((d) => ({ ...d, windowHours: v })), options: WINDOW_OPTIONS })] }), _jsxs("div", { className: "mt-3", children: [_jsx(Text, { className: "text-xs font-medium text-zinc-700 dark:text-zinc-300 mb-2", children: "Notify via" }), _jsx(Row, { className: "gap-2 flex-wrap", children: CHANNEL_OPTIONS.map((ch) => (_jsx("button", { type: "button", onClick: () => toggleChannel(ch.value), className: `px-3 py-1.5 rounded-full text-xs border transition-colors ${draft.notifyChannels.includes(ch.value)
|
|
122
|
+
? "bg-[var(--appkit-color-primary)] text-white border-transparent"
|
|
123
|
+
: "bg-white dark:bg-zinc-800 text-zinc-700 dark:text-zinc-300 border-zinc-200 dark:border-zinc-700"}`, children: ch.label }, ch.value))) })] }), _jsx(FormActions, { align: "right", className: "mt-4", children: _jsx(Button, { type: "submit", isLoading: createMutation.isPending, children: createMutation.isPending ? "Creating…" : "Create Alert" }) })] })] })), isLoading && (_jsx("div", { className: "space-y-2", children: [1, 2, 3].map((i) => (_jsx("div", { className: "h-16 rounded-lg bg-zinc-100 dark:bg-zinc-800 animate-pulse" }, i))) })), !isLoading && alerts.length === 0 && !showForm && (_jsx(Alert, { variant: "info", children: "No alerts configured. Create an alert to get notified when a key metric crosses a threshold." })), !isLoading && alerts.length > 0 && (_jsx("div", { className: "space-y-3", children: alerts.map((alert) => (_jsx(AlertCard, { alert: alert, onToggle: (id, isActive) => toggleMutation.mutate({ id, isActive }), onDelete: (id) => deleteMutation.mutate(id) }, alert.id))) }))] }));
|
|
124
|
+
}
|
|
@@ -3,6 +3,8 @@ import type { StackedViewShellProps } from "../../../ui";
|
|
|
3
3
|
export interface SellerAnalyticsViewProps extends Omit<StackedViewShellProps, "sections"> {
|
|
4
4
|
labels?: {
|
|
5
5
|
title?: string;
|
|
6
|
+
dateFrom?: string;
|
|
7
|
+
dateTo?: string;
|
|
6
8
|
};
|
|
7
9
|
renderDateRange?: (from: string, to: string, onChange: (from: string, to: string) => void) => React.ReactNode;
|
|
8
10
|
renderStats?: (isLoading: boolean) => React.ReactNode;
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import React from "react";
|
|
4
|
-
import { StackedViewShell } from "../../../ui";
|
|
4
|
+
import { StackedViewShell, Row, Text } from "../../../ui";
|
|
5
|
+
function DefaultDateRange({ from, to, onChange, labels, }) {
|
|
6
|
+
return (_jsxs(Row, { gap: "md", align: "center", wrap: true, className: "flex-wrap", children: [_jsxs("label", { className: "flex items-center gap-2 text-sm text-[var(--appkit-color-text-muted)]", children: [_jsx(Text, { size: "sm", variant: "secondary", children: labels.dateFrom ?? "From" }), _jsx("input", { type: "date", value: from, onChange: (e) => onChange(e.target.value, to), className: "appkit-input text-sm py-1.5 px-3" })] }), _jsxs("label", { className: "flex items-center gap-2 text-sm text-[var(--appkit-color-text-muted)]", children: [_jsx(Text, { size: "sm", variant: "secondary", children: labels.dateTo ?? "To" }), _jsx("input", { type: "date", value: to, onChange: (e) => onChange(from, e.target.value), className: "appkit-input text-sm py-1.5 px-3" })] })] }));
|
|
7
|
+
}
|
|
5
8
|
export function SellerAnalyticsView({ labels = {}, renderDateRange, renderStats, renderCharts, renderTopProducts, isLoading = false, ...rest }) {
|
|
6
9
|
const [from, setFrom] = React.useState("");
|
|
7
10
|
const [to, setTo] = React.useState("");
|
|
11
|
+
const handleDateChange = (f, t) => {
|
|
12
|
+
setFrom(f);
|
|
13
|
+
setTo(t);
|
|
14
|
+
};
|
|
15
|
+
const dateRange = (renderDateRange ?? ((f, t, onCh) => (_jsx(DefaultDateRange, { from: f, to: t, onChange: onCh, labels: labels }))))(from, to, handleDateChange);
|
|
16
|
+
const statsSection = (renderStats ?? (() => null))(isLoading);
|
|
17
|
+
const chartsSection = (renderCharts ?? (() => null))();
|
|
18
|
+
const topProducts = (renderTopProducts ?? (() => null))();
|
|
8
19
|
return (_jsx(StackedViewShell, { portal: "seller", ...rest, title: labels.title, sections: [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
renderStats?.(isLoading),
|
|
14
|
-
renderCharts?.(),
|
|
15
|
-
renderTopProducts?.(),
|
|
20
|
+
dateRange,
|
|
21
|
+
statsSection,
|
|
22
|
+
chartsSection,
|
|
23
|
+
topProducts,
|
|
16
24
|
] }));
|
|
17
25
|
}
|