@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,104 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback } from "react";
|
|
4
|
+
import { Plus } from "lucide-react";
|
|
5
|
+
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
|
+
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
7
|
+
import { BulkActionBar, Button, DataTable, Div, ListingToolbar, Pagination, Text, } from "../../../ui";
|
|
8
|
+
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
|
+
import { ROUTES } from "../../..";
|
|
10
|
+
import { toRecordArray, toRelativeDate, toRupees, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
|
|
11
|
+
import { TABLE_KEYS } from "../../../constants/table-keys";
|
|
12
|
+
const PAGE_SIZE = 25;
|
|
13
|
+
const DEFAULT_SORT = "-createdAt";
|
|
14
|
+
const SORT_OPTIONS = [
|
|
15
|
+
{ value: "-createdAt", label: "Newest" },
|
|
16
|
+
{ value: "createdAt", label: "Oldest" },
|
|
17
|
+
{ value: "productTitle", label: "Name A–Z" },
|
|
18
|
+
{ value: "-productTitle", label: "Name Z–A" },
|
|
19
|
+
{ value: "price", label: "Price: Low–High" },
|
|
20
|
+
{ value: "-price", label: "Price: High–Low" },
|
|
21
|
+
];
|
|
22
|
+
const COLUMNS = [
|
|
23
|
+
{
|
|
24
|
+
key: "title",
|
|
25
|
+
header: "Bundle",
|
|
26
|
+
render: (row) => _jsx(Text, { className: "text-sm font-medium", children: row.title }),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
key: "price",
|
|
30
|
+
header: "Price",
|
|
31
|
+
render: (row) => _jsx(Text, { className: "text-sm tabular-nums", children: row.price }),
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
key: "itemCount",
|
|
35
|
+
header: "Items",
|
|
36
|
+
render: (row) => (_jsx("span", { className: "inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium bg-violet-100 text-violet-700 dark:bg-violet-900/40 dark:text-violet-300 tabular-nums", children: row.itemCount })),
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
key: "status",
|
|
40
|
+
header: "Status",
|
|
41
|
+
render: (row) => (_jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium capitalize ${row.status === "active"
|
|
42
|
+
? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300"
|
|
43
|
+
: "bg-zinc-100 text-zinc-600 dark:bg-slate-800 dark:text-slate-400"}`, children: row.status })),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
key: "createdAt",
|
|
47
|
+
header: "Created",
|
|
48
|
+
render: (row) => _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: row.createdAt }),
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
export function SellerBundlesView({ onCreateClick, onBulkDelete, }) {
|
|
52
|
+
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
53
|
+
const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
|
|
54
|
+
const [view] = useState("table");
|
|
55
|
+
const commitSearch = useCallback(() => {
|
|
56
|
+
table.set(TABLE_KEYS.QUERY, searchInput.trim());
|
|
57
|
+
}, [searchInput, table]);
|
|
58
|
+
const resetAll = useCallback(() => {
|
|
59
|
+
table.setMany({ [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "" });
|
|
60
|
+
setSearchInput("");
|
|
61
|
+
}, [table]);
|
|
62
|
+
const hasActiveState = !!table.get(TABLE_KEYS.QUERY) || table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT;
|
|
63
|
+
const { rows, total, isLoading, errorMessage } = useSellerListingData({
|
|
64
|
+
queryKey: ["seller", "bundles"],
|
|
65
|
+
endpoint: SELLER_ENDPOINTS.PRODUCTS,
|
|
66
|
+
page: table.getNumber(TABLE_KEYS.PAGE, 1),
|
|
67
|
+
pageSize: PAGE_SIZE,
|
|
68
|
+
sorts: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT,
|
|
69
|
+
filters: "listingType==bundle",
|
|
70
|
+
q: table.get(TABLE_KEYS.QUERY) || undefined,
|
|
71
|
+
mapRows: (response) => toRecordArray(response.products).map((item, index) => ({
|
|
72
|
+
id: toStringValue(item.id, `bundle-${index}`),
|
|
73
|
+
raw: item,
|
|
74
|
+
title: toStringValue(item.productTitle ?? item.title, "Untitled bundle"),
|
|
75
|
+
price: toRupees(item.price),
|
|
76
|
+
itemCount: Array.isArray(item.bundleProductIds)
|
|
77
|
+
? item.bundleProductIds.length
|
|
78
|
+
: Number(item.bundleItemCount ?? 0),
|
|
79
|
+
status: toStringValue(item.status, "draft"),
|
|
80
|
+
createdAt: toRelativeDate(item.createdAt),
|
|
81
|
+
})),
|
|
82
|
+
getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
|
|
83
|
+
});
|
|
84
|
+
const currentPage = table.getNumber(TABLE_KEYS.PAGE, 1);
|
|
85
|
+
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
86
|
+
const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
|
|
87
|
+
const bulkActions = onBulkDelete
|
|
88
|
+
? [{
|
|
89
|
+
id: "bulk-delete",
|
|
90
|
+
label: "Delete selected",
|
|
91
|
+
variant: "danger",
|
|
92
|
+
onClick: async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); },
|
|
93
|
+
}]
|
|
94
|
+
: [];
|
|
95
|
+
const handleCreate = useCallback(() => {
|
|
96
|
+
if (onCreateClick) {
|
|
97
|
+
onCreateClick();
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
window.location.href = String(ROUTES.STORE.PRODUCTS_NEW);
|
|
101
|
+
}
|
|
102
|
+
}, [onCreateClick]);
|
|
103
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search bundles...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, showTableView: true, view: view, onViewChange: () => { }, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: handleCreate, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: "New Bundle" })] }) }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Div, { className: "mb-4 rounded-xl border border-red-200 bg-red-50 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), isLoading ? (_jsx(Div, { className: "space-y-2", children: Array.from({ length: 5 }).map((_, i) => (_jsx(Div, { className: "h-14 animate-pulse rounded-xl border border-zinc-100 dark:border-slate-700 bg-zinc-50 dark:bg-slate-800" }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "py-16 text-center", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: "No bundles yet \u2014 create a bundle to group multiple products together" }) })) : (_jsx(DataTable, { columns: COLUMNS, data: rows, keyExtractor: (r) => r.id, selectable: bulkActions.length > 0, selectedIds: selection.selectedIds, onSelectionChange: (ids) => selection.setSelectedIds(ids) }))] })] }));
|
|
104
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export interface SellerClassifiedViewProps {
|
|
2
|
+
onCreateClick?: () => void;
|
|
3
|
+
onBulkDelete?: (ids: string[]) => Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
export declare function SellerClassifiedView({ onCreateClick, onBulkDelete, }: SellerClassifiedViewProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback } from "react";
|
|
4
|
+
import { Plus } from "lucide-react";
|
|
5
|
+
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
|
+
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
7
|
+
import { BulkActionBar, Button, DataTable, Div, ListingToolbar, Pagination, Text, } from "../../../ui";
|
|
8
|
+
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
|
+
import { ROUTES } from "../../..";
|
|
10
|
+
import { toRecordArray, toRelativeDate, toRupees, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
|
|
11
|
+
import { TABLE_KEYS } from "../../../constants/table-keys";
|
|
12
|
+
const PAGE_SIZE = 25;
|
|
13
|
+
const DEFAULT_SORT = "-createdAt";
|
|
14
|
+
const SORT_OPTIONS = [
|
|
15
|
+
{ value: "-createdAt", label: "Newest" },
|
|
16
|
+
{ value: "createdAt", label: "Oldest" },
|
|
17
|
+
{ value: "productTitle", label: "Name A–Z" },
|
|
18
|
+
{ value: "price", label: "Price: Low–High" },
|
|
19
|
+
{ value: "-price", label: "Price: High–Low" },
|
|
20
|
+
];
|
|
21
|
+
const COLUMNS = [
|
|
22
|
+
{
|
|
23
|
+
key: "title",
|
|
24
|
+
header: "Listing",
|
|
25
|
+
render: (row) => _jsx(Text, { className: "text-sm font-medium", children: row.title }),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
key: "price",
|
|
29
|
+
header: "Price",
|
|
30
|
+
render: (row) => _jsx(Text, { className: "text-sm tabular-nums", children: row.price }),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
key: "city",
|
|
34
|
+
header: "Location",
|
|
35
|
+
render: (row) => (_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: row.city || "—" })),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
key: "acceptsShipping",
|
|
39
|
+
header: "Shipping",
|
|
40
|
+
render: (row) => (_jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${row.acceptsShipping
|
|
41
|
+
? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300"
|
|
42
|
+
: "bg-zinc-100 text-zinc-600 dark:bg-slate-800 dark:text-slate-400"}`, children: row.acceptsShipping ? "Ships" : "Meetup only" })),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
key: "status",
|
|
46
|
+
header: "Status",
|
|
47
|
+
render: (row) => (_jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium capitalize ${row.status === "active"
|
|
48
|
+
? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300"
|
|
49
|
+
: "bg-zinc-100 text-zinc-600 dark:bg-slate-800 dark:text-slate-400"}`, children: row.status })),
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
key: "createdAt",
|
|
53
|
+
header: "Created",
|
|
54
|
+
render: (row) => _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: row.createdAt }),
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
export function SellerClassifiedView({ onCreateClick, onBulkDelete, }) {
|
|
58
|
+
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
59
|
+
const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
|
|
60
|
+
const [view] = useState("table");
|
|
61
|
+
const commitSearch = useCallback(() => {
|
|
62
|
+
table.set(TABLE_KEYS.QUERY, searchInput.trim());
|
|
63
|
+
}, [searchInput, table]);
|
|
64
|
+
const resetAll = useCallback(() => {
|
|
65
|
+
table.setMany({ [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "" });
|
|
66
|
+
setSearchInput("");
|
|
67
|
+
}, [table]);
|
|
68
|
+
const hasActiveState = !!table.get(TABLE_KEYS.QUERY) || table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT;
|
|
69
|
+
const { rows, total, isLoading, errorMessage } = useSellerListingData({
|
|
70
|
+
queryKey: ["seller", "classified"],
|
|
71
|
+
endpoint: SELLER_ENDPOINTS.PRODUCTS,
|
|
72
|
+
page: table.getNumber(TABLE_KEYS.PAGE, 1),
|
|
73
|
+
pageSize: PAGE_SIZE,
|
|
74
|
+
sorts: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT,
|
|
75
|
+
filters: "listingType==classified",
|
|
76
|
+
q: table.get(TABLE_KEYS.QUERY) || undefined,
|
|
77
|
+
mapRows: (response) => toRecordArray(response.products).map((item, index) => {
|
|
78
|
+
const classified = (item.classified ?? {});
|
|
79
|
+
const meetupArea = (classified.meetupArea ?? {});
|
|
80
|
+
return {
|
|
81
|
+
id: toStringValue(item.id, `classified-${index}`),
|
|
82
|
+
raw: item,
|
|
83
|
+
title: toStringValue(item.productTitle ?? item.title, "Untitled"),
|
|
84
|
+
price: toRupees(item.price),
|
|
85
|
+
city: toStringValue(meetupArea.city ?? classified.city, ""),
|
|
86
|
+
acceptsShipping: Boolean(classified.acceptsShipping),
|
|
87
|
+
status: toStringValue(item.status, "draft"),
|
|
88
|
+
createdAt: toRelativeDate(item.createdAt),
|
|
89
|
+
};
|
|
90
|
+
}),
|
|
91
|
+
getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
|
|
92
|
+
});
|
|
93
|
+
const currentPage = table.getNumber(TABLE_KEYS.PAGE, 1);
|
|
94
|
+
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
95
|
+
const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
|
|
96
|
+
const bulkActions = onBulkDelete
|
|
97
|
+
? [{
|
|
98
|
+
id: "bulk-delete",
|
|
99
|
+
label: "Delete selected",
|
|
100
|
+
variant: "danger",
|
|
101
|
+
onClick: async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); },
|
|
102
|
+
}]
|
|
103
|
+
: [];
|
|
104
|
+
const handleCreate = useCallback(() => {
|
|
105
|
+
if (onCreateClick) {
|
|
106
|
+
onCreateClick();
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
window.location.href = String(ROUTES.STORE.CLASSIFIED_NEW);
|
|
110
|
+
}
|
|
111
|
+
}, [onCreateClick]);
|
|
112
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search classified listings...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, showTableView: true, view: view, onViewChange: () => { }, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: handleCreate, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: "New Classified" })] }) }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Div, { className: "mb-4 rounded-xl border border-red-200 bg-red-50 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), isLoading ? (_jsx(Div, { className: "space-y-2", children: Array.from({ length: 5 }).map((_, i) => (_jsx(Div, { className: "h-14 animate-pulse rounded-xl border border-zinc-100 dark:border-slate-700 bg-zinc-50 dark:bg-slate-800" }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "py-16 text-center", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: "No classified listings yet \u2014 post your first buy/sell/trade ad" }) })) : (_jsx(DataTable, { columns: COLUMNS, data: rows, keyExtractor: (r) => r.id, selectable: bulkActions.length > 0, selectedIds: selection.selectedIds, onSelectionChange: (ids) => selection.setSelectedIds(ids) }))] })] }));
|
|
113
|
+
}
|
|
@@ -1,11 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { StackedViewShell, Div, Text } from "../../../ui";
|
|
4
|
+
function DefaultStatsPlaceholder({ isLoading }) {
|
|
5
|
+
return (_jsx("div", { className: "grid grid-cols-2 gap-4 sm:grid-cols-4", children: [1, 2, 3, 4].map((i) => (_jsx(Div, { className: "rounded-xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] p-5", children: isLoading ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "h-3 w-16 animate-pulse rounded bg-[var(--appkit-color-border)]" }), _jsx("div", { className: "mt-3 h-6 w-20 animate-pulse rounded bg-[var(--appkit-color-border)]" })] })) : (_jsxs(_Fragment, { children: [_jsxs(Text, { size: "xs", variant: "secondary", className: "uppercase tracking-widest font-semibold", children: ["Stat ", i] }), _jsx(Text, { size: "xl", className: "mt-2 font-bold tabular-nums", children: "\u2014" })] })) }, i))) }));
|
|
6
|
+
}
|
|
7
|
+
function DefaultQuickActionsPlaceholder() {
|
|
8
|
+
return (_jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-4", children: [1, 2, 3, 4].map((i) => (_jsx(Div, { className: "h-12 animate-pulse rounded-xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-border)]" }, i))) }));
|
|
9
|
+
}
|
|
3
10
|
export function SellerDashboardView({ labels = {}, renderStats, renderQuickActions, renderRecentListings, renderRevenueChart, renderTopProducts, isLoading = false, ...rest }) {
|
|
11
|
+
const stats = (renderStats ?? ((busy) => _jsx(DefaultStatsPlaceholder, { isLoading: busy })))(isLoading);
|
|
12
|
+
const quickActions = (renderQuickActions ?? (() => _jsx(DefaultQuickActionsPlaceholder, {})))();
|
|
13
|
+
const revenueChart = (renderRevenueChart ?? (() => null))();
|
|
14
|
+
const topProducts = (renderTopProducts ?? (() => null))();
|
|
15
|
+
const recentListings = (renderRecentListings ?? (() => null))();
|
|
4
16
|
return (_jsx(StackedViewShell, { portal: "seller", ...rest, title: labels.title, sections: [
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
17
|
+
stats,
|
|
18
|
+
quickActions,
|
|
19
|
+
revenueChart,
|
|
20
|
+
topProducts,
|
|
21
|
+
recentListings,
|
|
10
22
|
] }));
|
|
11
23
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export interface SellerDigitalCodesViewProps {
|
|
2
|
+
onCreateClick?: () => void;
|
|
3
|
+
onBulkDelete?: (ids: string[]) => Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
export declare function SellerDigitalCodesView({ onCreateClick, onBulkDelete, }: SellerDigitalCodesViewProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback } from "react";
|
|
4
|
+
import { Plus } from "lucide-react";
|
|
5
|
+
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
|
+
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
7
|
+
import { BulkActionBar, Button, DataTable, Div, ListingToolbar, Pagination, Text, } from "../../../ui";
|
|
8
|
+
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
|
+
import { ROUTES } from "../../..";
|
|
10
|
+
import { toRecordArray, toRelativeDate, toRupees, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
|
|
11
|
+
import { TABLE_KEYS } from "../../../constants/table-keys";
|
|
12
|
+
const PAGE_SIZE = 25;
|
|
13
|
+
const DEFAULT_SORT = "-createdAt";
|
|
14
|
+
const SORT_OPTIONS = [
|
|
15
|
+
{ value: "-createdAt", label: "Newest" },
|
|
16
|
+
{ value: "createdAt", label: "Oldest" },
|
|
17
|
+
{ value: "productTitle", label: "Name A–Z" },
|
|
18
|
+
{ value: "price", label: "Price: Low–High" },
|
|
19
|
+
];
|
|
20
|
+
const COLUMNS = [
|
|
21
|
+
{
|
|
22
|
+
key: "title",
|
|
23
|
+
header: "Product",
|
|
24
|
+
render: (row) => _jsx(Text, { className: "text-sm font-medium", children: row.title }),
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
key: "price",
|
|
28
|
+
header: "Price",
|
|
29
|
+
render: (row) => _jsx(Text, { className: "text-sm tabular-nums", children: row.price }),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
key: "codePoolSize",
|
|
33
|
+
header: "Total Codes",
|
|
34
|
+
render: (row) => (_jsx(Text, { className: "text-sm tabular-nums", children: row.codePoolSize !== null ? row.codePoolSize : "—" })),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
key: "codesAvailable",
|
|
38
|
+
header: "Available",
|
|
39
|
+
render: (row) => (_jsx(Text, { className: "text-sm tabular-nums", children: row.codesAvailable !== null ? row.codesAvailable : "—" })),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
key: "deliveryMethod",
|
|
43
|
+
header: "Delivery",
|
|
44
|
+
render: (row) => (_jsx("span", { className: "inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium bg-zinc-100 text-zinc-600 dark:bg-slate-800 dark:text-slate-400 capitalize", children: row.deliveryMethod.replace(/-/g, " ") })),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
key: "status",
|
|
48
|
+
header: "Status",
|
|
49
|
+
render: (row) => (_jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium capitalize ${row.status === "active"
|
|
50
|
+
? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300"
|
|
51
|
+
: "bg-zinc-100 text-zinc-600 dark:bg-slate-800 dark:text-slate-400"}`, children: row.status })),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
key: "createdAt",
|
|
55
|
+
header: "Created",
|
|
56
|
+
render: (row) => _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: row.createdAt }),
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
export function SellerDigitalCodesView({ onCreateClick, onBulkDelete, }) {
|
|
60
|
+
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
61
|
+
const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
|
|
62
|
+
const [view] = useState("table");
|
|
63
|
+
const commitSearch = useCallback(() => {
|
|
64
|
+
table.set(TABLE_KEYS.QUERY, searchInput.trim());
|
|
65
|
+
}, [searchInput, table]);
|
|
66
|
+
const resetAll = useCallback(() => {
|
|
67
|
+
table.setMany({ [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "" });
|
|
68
|
+
setSearchInput("");
|
|
69
|
+
}, [table]);
|
|
70
|
+
const hasActiveState = !!table.get(TABLE_KEYS.QUERY) || table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT;
|
|
71
|
+
const { rows, total, isLoading, errorMessage } = useSellerListingData({
|
|
72
|
+
queryKey: ["seller", "digital-codes"],
|
|
73
|
+
endpoint: SELLER_ENDPOINTS.PRODUCTS,
|
|
74
|
+
page: table.getNumber(TABLE_KEYS.PAGE, 1),
|
|
75
|
+
pageSize: PAGE_SIZE,
|
|
76
|
+
sorts: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT,
|
|
77
|
+
filters: "listingType==digital-code",
|
|
78
|
+
q: table.get(TABLE_KEYS.QUERY) || undefined,
|
|
79
|
+
mapRows: (response) => toRecordArray(response.products).map((item, index) => {
|
|
80
|
+
const dc = (item.digitalCode ?? {});
|
|
81
|
+
return {
|
|
82
|
+
id: toStringValue(item.id, `dc-${index}`),
|
|
83
|
+
raw: item,
|
|
84
|
+
title: toStringValue(item.productTitle ?? item.title, "Untitled"),
|
|
85
|
+
price: toRupees(item.price),
|
|
86
|
+
codePoolSize: typeof dc.codePoolSize === "number" ? dc.codePoolSize : null,
|
|
87
|
+
codesAvailable: typeof dc.codesAvailable === "number" ? dc.codesAvailable : null,
|
|
88
|
+
deliveryMethod: toStringValue(dc.codeDeliveryMethod, "auto-claim"),
|
|
89
|
+
status: toStringValue(item.status, "draft"),
|
|
90
|
+
createdAt: toRelativeDate(item.createdAt),
|
|
91
|
+
};
|
|
92
|
+
}),
|
|
93
|
+
getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
|
|
94
|
+
});
|
|
95
|
+
const currentPage = table.getNumber(TABLE_KEYS.PAGE, 1);
|
|
96
|
+
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
97
|
+
const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
|
|
98
|
+
const bulkActions = onBulkDelete
|
|
99
|
+
? [{
|
|
100
|
+
id: "bulk-delete",
|
|
101
|
+
label: "Delete selected",
|
|
102
|
+
variant: "danger",
|
|
103
|
+
onClick: async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); },
|
|
104
|
+
}]
|
|
105
|
+
: [];
|
|
106
|
+
const handleCreate = useCallback(() => {
|
|
107
|
+
if (onCreateClick) {
|
|
108
|
+
onCreateClick();
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
window.location.href = String(ROUTES.STORE.DIGITAL_CODES_NEW);
|
|
112
|
+
}
|
|
113
|
+
}, [onCreateClick]);
|
|
114
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search digital code products...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, showTableView: true, view: view, onViewChange: () => { }, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: handleCreate, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: "New Digital Code" })] }) }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Div, { className: "mb-4 rounded-xl border border-red-200 bg-red-50 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), isLoading ? (_jsx(Div, { className: "space-y-2", children: Array.from({ length: 5 }).map((_, i) => (_jsx(Div, { className: "h-14 animate-pulse rounded-xl border border-zinc-100 dark:border-slate-700 bg-zinc-50 dark:bg-slate-800" }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "py-16 text-center", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: "No digital code products yet \u2014 sell Steam keys, gift cards, and more" }) })) : (_jsx(DataTable, { columns: COLUMNS, data: rows, keyExtractor: (r) => r.id, selectable: bulkActions.length > 0, selectedIds: selection.selectedIds, onSelectionChange: (ids) => selection.setSelectedIds(ids) }))] })] }));
|
|
115
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface GoogleConfigDraft {
|
|
2
|
+
placeId: string;
|
|
3
|
+
businessName: string;
|
|
4
|
+
isConnected: boolean;
|
|
5
|
+
averageRating?: number;
|
|
6
|
+
totalReviews?: number;
|
|
7
|
+
lastSyncedAt?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SellerGoogleReviewsViewProps {
|
|
10
|
+
onSave?: (data: Partial<GoogleConfigDraft>) => Promise<void>;
|
|
11
|
+
onSync?: () => Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export declare function SellerGoogleReviewsView({ onSave, onSync, }: SellerGoogleReviewsViewProps): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useState } from "react";
|
|
4
|
+
import { RefreshCw } from "lucide-react";
|
|
5
|
+
import { Alert, Button, Div, FormField, Heading, Section, Stack, Text, Toggle, } from "../../../ui";
|
|
6
|
+
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
7
|
+
const EMPTY_DRAFT = {
|
|
8
|
+
placeId: "",
|
|
9
|
+
businessName: "",
|
|
10
|
+
isConnected: false,
|
|
11
|
+
};
|
|
12
|
+
export function SellerGoogleReviewsView({ onSave, onSync, }) {
|
|
13
|
+
const [draft, setDraft] = useState(EMPTY_DRAFT);
|
|
14
|
+
const [loading, setLoading] = useState(true);
|
|
15
|
+
const [saving, setSaving] = useState(false);
|
|
16
|
+
const [syncing, setSyncing] = useState(false);
|
|
17
|
+
const [saveMessage, setSaveMessage] = useState(null);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
fetch(SELLER_ENDPOINTS.GOOGLE_REVIEWS, { credentials: "include" })
|
|
20
|
+
.then((r) => r.json())
|
|
21
|
+
.then((j) => {
|
|
22
|
+
const data = j?.data ?? j ?? {};
|
|
23
|
+
setDraft({
|
|
24
|
+
placeId: String(data.placeId ?? ""),
|
|
25
|
+
businessName: String(data.businessName ?? ""),
|
|
26
|
+
isConnected: Boolean(data.isConnected),
|
|
27
|
+
averageRating: typeof data.averageRating === "number" ? data.averageRating : undefined,
|
|
28
|
+
totalReviews: typeof data.totalReviews === "number" ? data.totalReviews : undefined,
|
|
29
|
+
lastSyncedAt: data.lastSyncedAt ? String(data.lastSyncedAt) : undefined,
|
|
30
|
+
});
|
|
31
|
+
})
|
|
32
|
+
.catch(() => { })
|
|
33
|
+
.finally(() => setLoading(false));
|
|
34
|
+
}, []);
|
|
35
|
+
const handleSave = useCallback(async () => {
|
|
36
|
+
setSaving(true);
|
|
37
|
+
setSaveMessage(null);
|
|
38
|
+
try {
|
|
39
|
+
if (onSave) {
|
|
40
|
+
await onSave(draft);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const res = await fetch(SELLER_ENDPOINTS.GOOGLE_REVIEWS, {
|
|
44
|
+
method: "PUT",
|
|
45
|
+
headers: { "Content-Type": "application/json" },
|
|
46
|
+
credentials: "include",
|
|
47
|
+
body: JSON.stringify({
|
|
48
|
+
placeId: draft.placeId.trim() || undefined,
|
|
49
|
+
businessName: draft.businessName.trim() || undefined,
|
|
50
|
+
isConnected: draft.isConnected,
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
setSaveMessage({ text: res.ok ? "Settings saved." : "Save failed.", ok: res.ok });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
setSaveMessage({ text: "Save failed. Please try again.", ok: false });
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
setSaving(false);
|
|
61
|
+
}
|
|
62
|
+
}, [draft, onSave]);
|
|
63
|
+
const handleSync = useCallback(async () => {
|
|
64
|
+
setSyncing(true);
|
|
65
|
+
setSaveMessage(null);
|
|
66
|
+
try {
|
|
67
|
+
if (onSync) {
|
|
68
|
+
await onSync();
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const res = await fetch(SELLER_ENDPOINTS.GOOGLE_REVIEWS_SYNC, {
|
|
72
|
+
method: "POST",
|
|
73
|
+
credentials: "include",
|
|
74
|
+
});
|
|
75
|
+
setSaveMessage({ text: res.ok ? "Sync queued." : "Sync failed.", ok: res.ok });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
setSaveMessage({ text: "Sync failed. Please try again.", ok: false });
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
setSyncing(false);
|
|
83
|
+
}
|
|
84
|
+
}, [onSync]);
|
|
85
|
+
if (loading) {
|
|
86
|
+
return (_jsx("div", { className: "space-y-4 py-6 px-4", children: Array.from({ length: 3 }).map((_, i) => (_jsx("div", { className: "h-12 animate-pulse rounded-xl border border-zinc-100 dark:border-slate-700 bg-zinc-50 dark:bg-slate-800" }, i))) }));
|
|
87
|
+
}
|
|
88
|
+
const formattedLastSync = draft.lastSyncedAt
|
|
89
|
+
? new Date(draft.lastSyncedAt).toLocaleString("en-IN", {
|
|
90
|
+
dateStyle: "medium",
|
|
91
|
+
timeStyle: "short",
|
|
92
|
+
})
|
|
93
|
+
: "Never";
|
|
94
|
+
return (_jsx("div", { className: "py-4 px-3 sm:px-4 max-w-2xl", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Section, { children: [_jsx(Heading, { level: 3, className: "mb-1", children: "Google Business Settings" }), _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] mb-4", children: "Connect your Google Business profile to sync reviews to your storefront." }), _jsxs(Stack, { gap: "md", children: [_jsx(Toggle, { checked: draft.isConnected, onChange: (v) => setDraft((d) => ({ ...d, isConnected: v })), label: "Show Google reviews on my store page" }), _jsx(FormField, { name: "placeId", label: "Google Place ID", type: "text", value: draft.placeId, onChange: (v) => setDraft((d) => ({ ...d, placeId: v })), placeholder: "ChIJ\u2026", helpText: "Find your Place ID at developers.google.com/maps/documentation/places/web-service/place-id" }), _jsx(FormField, { name: "businessName", label: "Business name", type: "text", value: draft.businessName, onChange: (v) => setDraft((d) => ({ ...d, businessName: v })), placeholder: "Pok\u00E9mon Palace" })] })] }), (draft.averageRating !== undefined || draft.totalReviews !== undefined) && (_jsxs(Section, { children: [_jsx(Heading, { level: 3, className: "mb-3", children: "Review Stats" }), _jsxs("div", { className: "flex flex-wrap gap-6", children: [draft.averageRating !== undefined && (_jsxs(Div, { children: [_jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)] uppercase tracking-wide", children: "Avg rating" }), _jsx(Text, { className: "text-2xl font-bold tabular-nums", children: draft.averageRating.toFixed(1) })] })), draft.totalReviews !== undefined && (_jsxs(Div, { children: [_jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)] uppercase tracking-wide", children: "Total reviews" }), _jsx(Text, { className: "text-2xl font-bold tabular-nums", children: draft.totalReviews })] })), _jsxs(Div, { children: [_jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)] uppercase tracking-wide", children: "Last synced" }), _jsx(Text, { className: "text-sm", children: formattedLastSync })] })] })] })), saveMessage && (_jsx(Alert, { variant: saveMessage.ok ? "success" : "error", children: saveMessage.text })), _jsxs("div", { className: "flex items-center justify-between gap-3 border-t border-[var(--appkit-color-border)] pt-4", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: handleSync, isLoading: syncing, disabled: saving || !draft.placeId.trim(), className: "flex items-center gap-1.5", children: [_jsx(RefreshCw, { className: "h-3.5 w-3.5" }), "Sync now"] }), _jsx(Button, { variant: "primary", size: "sm", onClick: handleSave, isLoading: saving, disabled: syncing, children: "Save Settings" })] })] }) }));
|
|
95
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface SellerGroupedListingsViewProps {
|
|
2
|
+
onCreateClick?: () => void;
|
|
3
|
+
onEditClick?: (id: string) => void;
|
|
4
|
+
onDeleteClick?: (id: string) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare function SellerGroupedListingsView({ onCreateClick, onEditClick, onDeleteClick, }: SellerGroupedListingsViewProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback } from "react";
|
|
4
|
+
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
5
|
+
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
6
|
+
import { Badge, BulkActionBar, Button, ListingToolbar, Pagination } from "../../../ui";
|
|
7
|
+
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
|
+
import { toRecordArray, toRelativeDate, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
|
|
9
|
+
import { DataTable } from "../../admin/components/DataTable";
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Config
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
const PAGE_SIZE = 25;
|
|
14
|
+
const DEFAULT_SORT = "-createdAt";
|
|
15
|
+
const SORT_OPTIONS = [
|
|
16
|
+
{ value: "-createdAt", label: "Newest" },
|
|
17
|
+
{ value: "createdAt", label: "Oldest" },
|
|
18
|
+
{ value: "title", label: "Title A–Z" },
|
|
19
|
+
{ value: "-title", label: "Title Z–A" },
|
|
20
|
+
];
|
|
21
|
+
const THEME_LABELS = {
|
|
22
|
+
related: "Related",
|
|
23
|
+
character: "Character",
|
|
24
|
+
lineage: "Lineage",
|
|
25
|
+
set: "Set",
|
|
26
|
+
generic: "Generic",
|
|
27
|
+
};
|
|
28
|
+
const VISIBILITY_BADGE = {
|
|
29
|
+
visible: "success",
|
|
30
|
+
hidden: "warning",
|
|
31
|
+
};
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Columns
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
const COLUMNS = [
|
|
36
|
+
{
|
|
37
|
+
key: "title",
|
|
38
|
+
header: "Title",
|
|
39
|
+
render: (r) => _jsx("span", { className: "font-medium text-zinc-900 dark:text-zinc-100", children: r.title }),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
key: "groupTheme",
|
|
43
|
+
header: "Theme",
|
|
44
|
+
render: (r) => (_jsx(Badge, { variant: "default", children: THEME_LABELS[r.groupTheme] ?? r.groupTheme })),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
key: "productCount",
|
|
48
|
+
header: "Products",
|
|
49
|
+
render: (r) => _jsx("span", { className: "text-sm text-zinc-600 dark:text-zinc-400", children: r.productCount }),
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
key: "visibilityStatus",
|
|
53
|
+
header: "Visibility",
|
|
54
|
+
render: (r) => (_jsx(Badge, { variant: VISIBILITY_BADGE[r.visibilityStatus] ?? "default", children: r.visibilityStatus === "visible" ? "Visible" : "Hidden" })),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
key: "isActive",
|
|
58
|
+
header: "Status",
|
|
59
|
+
render: (r) => (_jsx(Badge, { variant: r.isActive ? "success" : "default", children: r.isActive ? "Active" : "Inactive" })),
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
key: "createdAt",
|
|
63
|
+
header: "Created",
|
|
64
|
+
render: (r) => _jsx("span", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: r.createdAt }),
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
export function SellerGroupedListingsView({ onCreateClick, onEditClick, onDeleteClick, }) {
|
|
68
|
+
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
69
|
+
const [searchInput, setSearchInput] = useState(table.get("q") || "");
|
|
70
|
+
const resetAll = useCallback(() => {
|
|
71
|
+
table.setMany({ q: "", sort: "" });
|
|
72
|
+
setSearchInput("");
|
|
73
|
+
}, [table]);
|
|
74
|
+
const commitSearch = useCallback(() => {
|
|
75
|
+
table.set("q", searchInput.trim());
|
|
76
|
+
}, [searchInput, table]);
|
|
77
|
+
const hasActiveState = !!table.get("q") || table.get("sort") !== DEFAULT_SORT;
|
|
78
|
+
const { rows, total, isLoading, errorMessage } = useSellerListingData({
|
|
79
|
+
queryKey: ["seller", "grouped-listings"],
|
|
80
|
+
endpoint: SELLER_ENDPOINTS.GROUPED_LISTINGS,
|
|
81
|
+
page: table.getNumber("page", 1),
|
|
82
|
+
pageSize: PAGE_SIZE,
|
|
83
|
+
sorts: table.get("sort") || DEFAULT_SORT,
|
|
84
|
+
q: table.get("q") || undefined,
|
|
85
|
+
mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
|
|
86
|
+
id: toStringValue(item.id, `group-${index}`),
|
|
87
|
+
title: toStringValue(item.title, "Untitled group"),
|
|
88
|
+
groupTheme: toStringValue(item.groupTheme, "generic"),
|
|
89
|
+
productCount: Array.isArray(item.productIds) ? item.productIds.length : 0,
|
|
90
|
+
isActive: item.isActive === true,
|
|
91
|
+
visibilityStatus: toStringValue(item.visibilityStatus, "hidden"),
|
|
92
|
+
createdAt: toRelativeDate(item.createdAt),
|
|
93
|
+
})),
|
|
94
|
+
getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
|
|
95
|
+
});
|
|
96
|
+
const currentPage = table.getNumber("page", 1);
|
|
97
|
+
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
98
|
+
const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
|
|
99
|
+
const bulkActions = [
|
|
100
|
+
{
|
|
101
|
+
id: "delete",
|
|
102
|
+
label: "Delete selected",
|
|
103
|
+
variant: "danger",
|
|
104
|
+
onClick: () => {
|
|
105
|
+
for (const id of selection.selectedIds)
|
|
106
|
+
onDeleteClick?.(id);
|
|
107
|
+
selection.clearSelection();
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { searchValue: searchInput, searchPlaceholder: "Search grouped listings", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, onResetAll: resetAll, hasActiveState: hasActiveState, extra: onCreateClick ? (_jsx(Button, { size: "sm", onClick: onCreateClick, children: "+ New Group" })) : null }), selection.selectedCount > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx(DataTable, { columns: COLUMNS, rows: rows, isLoading: isLoading, emptyLabel: errorMessage ?? "No grouped listings yet", selectedIds: selection.selectedIdSet, onToggleSelect: (id, _selected) => selection.toggle(id), onToggleSelectAll: (_next) => selection.toggleAll(), renderRowActions: (row) => (_jsxs("div", { className: "flex gap-1", children: [_jsx(Button, { size: "sm", variant: "ghost", onClick: () => onEditClick?.(row.id), children: "Edit" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => onDeleteClick?.(row.id), children: "Delete" })] })) })] }));
|
|
112
|
+
}
|