@mohasinac/appkit 2.7.50 → 2.7.52
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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import React, { useState, useCallback } from "react";
|
|
4
4
|
import { useActionDispatch } from "../../../react/hooks/use-action-dispatch";
|
|
5
|
-
import { Pencil, Trash2, Printer, MapPin } from "lucide-react";
|
|
5
|
+
import { Eye, EyeOff, Pencil, Trash2, Printer, MapPin } from "lucide-react";
|
|
6
6
|
import { PhysicalLocationModal } from "./PhysicalLocationModal";
|
|
7
7
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
8
8
|
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
@@ -16,7 +16,7 @@ import { DataTable } from "../../admin/components/DataTable";
|
|
|
16
16
|
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
17
17
|
import { SellerProductsCards } from "./SellerProductsCards";
|
|
18
18
|
import { SellerProductsFilterDrawer } from "./SellerProductsFilterDrawer";
|
|
19
|
-
import { KIND_BADGE_VARIANT
|
|
19
|
+
import { KIND_BADGE_VARIANT } from "./seller-products-styles";
|
|
20
20
|
const PAGE_SIZE = 25;
|
|
21
21
|
const FILTER_KEYS = [
|
|
22
22
|
"status",
|
|
@@ -54,26 +54,6 @@ function TypeDropdown({ active, onChange, }) {
|
|
|
54
54
|
];
|
|
55
55
|
return (_jsxs(Row, { className: "gap-2 px-3 lg:px-4 py-2 items-center border-b border-[var(--appkit-color-border)]", children: [_jsx(Text, { className: "text-xs font-semibold uppercase tracking-wide text-[var(--appkit-color-text-muted)]", children: "Listing type" }), _jsx("select", { value: active, onChange: (e) => onChange(e.target.value), className: "rounded border border-[var(--appkit-color-border)] bg-transparent px-2 py-1 text-sm sm:max-w-xs", "aria-label": "Filter by listing type", children: options.map((o) => (_jsx("option", { value: o.value, children: o.label }, o.value))) })] }));
|
|
56
56
|
}
|
|
57
|
-
// Kept for backward-compat callers; thin wrapper around TypeDropdown.
|
|
58
|
-
function TypeChips({ active, onChange, }) {
|
|
59
|
-
const chips = [
|
|
60
|
-
{ kind: "all", label: "All" },
|
|
61
|
-
{ kind: "standard", label: "Standard" },
|
|
62
|
-
{ kind: "auction", label: "Auction" },
|
|
63
|
-
{ kind: "pre-order", label: "Pre-order" },
|
|
64
|
-
{ kind: "prize-draw", label: "Prize Draw" },
|
|
65
|
-
{ kind: "bundle", label: "Bundle" },
|
|
66
|
-
{ kind: "classified", label: "Classified" },
|
|
67
|
-
{ kind: "digital-code", label: "Digital Code" },
|
|
68
|
-
{ kind: "live", label: "Live" },
|
|
69
|
-
];
|
|
70
|
-
return (_jsx(Row, { className: "gap-2 px-3 lg:px-4 py-2 overflow-x-auto border-b border-[var(--appkit-color-border)]", children: chips.map(({ kind, label }) => (_jsx("button", { type: "button", onClick: () => onChange(kind), className: [
|
|
71
|
-
"flex-shrink-0 rounded-full px-3 py-1 text-xs font-medium border transition-colors",
|
|
72
|
-
active === kind
|
|
73
|
-
? "bg-[var(--appkit-color-primary)] text-white border-[var(--appkit-color-primary)]"
|
|
74
|
-
: "border-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)] hover:border-[var(--appkit-color-primary)] hover:text-[var(--appkit-color-primary)]",
|
|
75
|
-
].join(" "), children: label }, kind))) }));
|
|
76
|
-
}
|
|
77
57
|
const PRODUCT_COLUMNS = [
|
|
78
58
|
{
|
|
79
59
|
key: "thumbnail",
|
|
@@ -127,8 +107,12 @@ export function SellerProductsView({ onDeleteProduct, onCreateClick, children, .
|
|
|
127
107
|
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
128
108
|
const [searchInput, setSearchInput] = useState(table.get("q") || "");
|
|
129
109
|
const [filterOpen, setFilterOpen] = useState(false);
|
|
130
|
-
|
|
110
|
+
// listingKind is URL-driven so it survives navigation and back/forward
|
|
111
|
+
const listingKind = (table.get("listingType") || "all");
|
|
131
112
|
const [deletingId, setDeletingId] = useState(null);
|
|
113
|
+
const [deletedIds, setDeletedIds] = useState(new Set());
|
|
114
|
+
const [publishingId, setPublishingId] = useState(null);
|
|
115
|
+
const [statusOverrides, setStatusOverrides] = useState(new Map());
|
|
132
116
|
const [setLocationOpen, setSetLocationOpen] = useState(false);
|
|
133
117
|
const [pendingFilters, setPendingFilters] = useState(() => Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
|
|
134
118
|
const openFilters = useCallback(() => {
|
|
@@ -146,39 +130,33 @@ export function SellerProductsView({ onDeleteProduct, onCreateClick, children, .
|
|
|
146
130
|
setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, ""])));
|
|
147
131
|
}, []);
|
|
148
132
|
const resetAll = useCallback(() => {
|
|
149
|
-
const updates = { q: "", sort: "" };
|
|
133
|
+
const updates = { q: "", sort: "", listingType: "", showSold: "" };
|
|
150
134
|
for (const k of FILTER_KEYS)
|
|
151
135
|
updates[k] = "";
|
|
152
136
|
table.setMany(updates);
|
|
153
137
|
setSearchInput("");
|
|
154
|
-
setListingKind("all");
|
|
155
138
|
}, [table]);
|
|
156
139
|
const commitSearch = useCallback(() => {
|
|
157
140
|
table.set("q", searchInput.trim());
|
|
158
141
|
}, [searchInput, table]);
|
|
159
142
|
const handleKindChange = useCallback((kind) => {
|
|
160
|
-
|
|
161
|
-
table.
|
|
143
|
+
// setMany prevents the double router.replace race condition (audit-double-navigation)
|
|
144
|
+
table.setMany({ listingType: kind === "all" ? "" : kind, page: "1" });
|
|
162
145
|
}, [table]);
|
|
146
|
+
const showSold = table.get("showSold") === "true";
|
|
163
147
|
const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
|
|
164
148
|
const hasActiveState = !!table.get("q") ||
|
|
165
149
|
table.get("sort") !== DEFAULT_SORT ||
|
|
166
150
|
activeFilterCount > 0 ||
|
|
167
|
-
listingKind !== "all"
|
|
151
|
+
listingKind !== "all" ||
|
|
152
|
+
showSold;
|
|
168
153
|
const statusRaw = table.get("status");
|
|
169
154
|
const statusFilter = statusRaw && statusRaw !== "All" ? `status==${statusRaw}` : undefined;
|
|
170
155
|
// SB1-G — single-field listingType clause. The repository's Sieve aliases
|
|
171
156
|
// accept both `==auction|preorder|standard` and `==pre-order` directly.
|
|
172
|
-
const kindFilter = listingKind === "
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
? "listingType==pre-order"
|
|
176
|
-
: listingKind === "prize-draw"
|
|
177
|
-
? "listingType==prize-draw"
|
|
178
|
-
: listingKind === "standard"
|
|
179
|
-
? "listingType==standard"
|
|
180
|
-
: undefined;
|
|
181
|
-
const filters = [statusFilter, kindFilter].filter(Boolean).join(",") || undefined;
|
|
157
|
+
const kindFilter = listingKind === "all" ? undefined : `listingType==${listingKind}`;
|
|
158
|
+
const soldFilter = showSold ? undefined : "isSold==false";
|
|
159
|
+
const filters = [statusFilter, kindFilter, soldFilter].filter(Boolean).join(",") || undefined;
|
|
182
160
|
const { rows, total, isLoading, errorMessage } = useSellerListingData({
|
|
183
161
|
queryKey: ["seller", "products", "listing", listingKind],
|
|
184
162
|
endpoint: SELLER_ENDPOINTS.PRODUCTS,
|
|
@@ -217,7 +195,10 @@ export function SellerProductsView({ onDeleteProduct, onCreateClick, children, .
|
|
|
217
195
|
});
|
|
218
196
|
const currentPage = table.getNumber("page", 1);
|
|
219
197
|
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
220
|
-
const
|
|
198
|
+
const visibleRows = rows
|
|
199
|
+
.filter((r) => !deletedIds.has(r.id))
|
|
200
|
+
.map((r) => statusOverrides.has(r.id) ? { ...r, status: statusOverrides.get(r.id) } : r);
|
|
201
|
+
const selection = useBulkSelection({ items: visibleRows, keyExtractor: (r) => r.id });
|
|
221
202
|
if (hasChildren) {
|
|
222
203
|
return (_jsx(ListingViewShell, { portal: "seller", ...props, children: children }));
|
|
223
204
|
}
|
|
@@ -231,22 +212,14 @@ export function SellerProductsView({ onDeleteProduct, onCreateClick, children, .
|
|
|
231
212
|
: String(ROUTES.STORE.PRODUCTS_EDIT(row.id));
|
|
232
213
|
void dispatch({ type: "NAVIGATE", href });
|
|
233
214
|
};
|
|
234
|
-
// S-STORE-2-D — row click navigates to public detail/preview, NOT edit.
|
|
235
|
-
// Edit is only available via the per-row "..." action menu.
|
|
236
|
-
const handleRowClick = (row) => {
|
|
237
|
-
const href = row.listingKind === "auction"
|
|
238
|
-
? `/auctions/${row.id}`
|
|
239
|
-
: row.listingKind === "pre-order"
|
|
240
|
-
? `/pre-orders/${row.id}`
|
|
241
|
-
: `/products/${row.id}`;
|
|
242
|
-
void dispatch({ type: "NAVIGATE", href });
|
|
243
|
-
};
|
|
244
215
|
const handleDelete = async (row) => {
|
|
245
216
|
if (!onDeleteProduct)
|
|
246
217
|
return;
|
|
247
218
|
setDeletingId(row.id);
|
|
248
219
|
try {
|
|
249
220
|
await onDeleteProduct(row.id);
|
|
221
|
+
// Optimistically remove from list — avoids a full refetch
|
|
222
|
+
setDeletedIds((prev) => new Set([...prev, row.id]));
|
|
250
223
|
}
|
|
251
224
|
finally {
|
|
252
225
|
setDeletingId(null);
|
|
@@ -264,6 +237,24 @@ export function SellerProductsView({ onDeleteProduct, onCreateClick, children, .
|
|
|
264
237
|
handleEdit({ ...row, id: newId });
|
|
265
238
|
}
|
|
266
239
|
};
|
|
240
|
+
const handleTogglePublish = async (row) => {
|
|
241
|
+
const currentStatus = statusOverrides.get(row.id) ?? row.status;
|
|
242
|
+
const newStatus = currentStatus === "published" ? "draft" : "published";
|
|
243
|
+
setPublishingId(row.id);
|
|
244
|
+
try {
|
|
245
|
+
const res = await fetch(`/api/store/products/${row.id}`, {
|
|
246
|
+
method: "PATCH",
|
|
247
|
+
headers: { "Content-Type": "application/json" },
|
|
248
|
+
body: JSON.stringify({ status: newStatus }),
|
|
249
|
+
}).catch(() => null);
|
|
250
|
+
if (res?.ok) {
|
|
251
|
+
setStatusOverrides((prev) => new Map([...prev, [row.id, newStatus]]));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
setPublishingId(null);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
267
258
|
const handleBulkPrintLabels = useCallback(() => {
|
|
268
259
|
const ids = selection.selectedIds.join(",");
|
|
269
260
|
void dispatch({
|
|
@@ -293,15 +284,18 @@ export function SellerProductsView({ onDeleteProduct, onCreateClick, children, .
|
|
|
293
284
|
onClick: () => setSetLocationOpen(true),
|
|
294
285
|
},
|
|
295
286
|
];
|
|
296
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products by name\u2026", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState,
|
|
287
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products by name\u2026", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, toggles: [
|
|
288
|
+
{ label: "Show sold", active: showSold, onChange: (next) => table.set("showSold", next ? "true" : "") },
|
|
289
|
+
], extra: onCreateClick ? (_jsx(Button, { variant: "primary", size: "sm", onClick: onCreateClick, children: "+ New Listing" })) : null }), _jsx(TypeDropdown, { active: listingKind, onChange: handleKindChange }), totalPages > 1 && (_jsx(Div, { className: "sticky z-10 flex justify-center bg-[var(--appkit-color-surface)]/95 backdrop-blur-sm border-b border-[var(--appkit-color-border)] px-3 py-1.5", style: { top: "calc(var(--header-height, 0px) + 44px)" }, children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), selection.selectedIds.length > 0 && (_jsx(Div, { className: "sticky z-20 px-3 lg:px-4 py-2 bg-[var(--appkit-color-surface)]/95 backdrop-blur-sm border-b border-[var(--appkit-color-border)]", style: { top: "calc(var(--header-height, 0px) + 88px)" }, children: _jsx(BulkActionBar, { selectedCount: selection.selectedIds.length, onClearSelection: selection.clearSelection, actions: bulkActions }) })), _jsxs(Div, { className: "py-4 px-3 lg:px-4", children: [errorMessage && (_jsx(Alert, { variant: "error", className: "mb-4", children: errorMessage })), view !== "table" && (_jsx(SellerProductsCards, { view: view, rows: visibleRows, isLoading: isLoading, listingKind: listingKind, selectedIds: selection.selectedIdSet, toggle: selection.toggle, onEdit: handleEdit, onDuplicate: (row) => void handleDuplicate(row), onDelete: onDeleteProduct ? (row) => void handleDelete(row) : undefined })), view === "table" && (_jsx(DataTable, { columns: PRODUCT_COLUMNS, rows: visibleRows, isLoading: isLoading, emptyLabel: listingKind !== "all"
|
|
297
290
|
? `No ${listingKind} listings found`
|
|
298
|
-
: "No products listed yet", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (
|
|
291
|
+
: "No products listed yet", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: () => selection.toggleAll(), getRowHref: (row) =>
|
|
299
292
|
// S-STORE-2-D — row click → public detail/preview, not edit.
|
|
300
293
|
row.listingKind === "auction"
|
|
301
294
|
? `/auctions/${row.id}`
|
|
302
295
|
: row.listingKind === "pre-order"
|
|
303
296
|
? `/pre-orders/${row.id}`
|
|
304
|
-
: `/products/${row.id}`, renderRowActions:
|
|
305
|
-
|
|
306
|
-
:
|
|
297
|
+
: `/products/${row.id}`, renderRowActions: (row) => {
|
|
298
|
+
const isPublished = (statusOverrides.get(row.id) ?? row.status) === "published";
|
|
299
|
+
return (_jsxs(Row, { className: "gap-1", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); handleEdit(row); }, "aria-label": ACTIONS.STORE["edit-listing"].ariaLabel, children: _jsx(Pencil, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); void handleTogglePublish(row); }, "aria-label": isPublished ? "Unpublish" : "Publish", title: isPublished ? "Unpublish" : "Publish", disabled: publishingId === row.id, children: isPublished ? _jsx(EyeOff, { className: "w-4 h-4" }) : _jsx(Eye, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); void handleDuplicate(row); }, "aria-label": "Duplicate listing", title: "Duplicate", children: "\u29C9" }), onDeleteProduct && (_jsx(Button, { variant: "ghost", size: "sm", action: ACTIONS.STORE["delete-listing"], onClick: (e) => { e.stopPropagation(); void handleDelete(row); }, disabled: deletingId === row.id, className: "text-[var(--appkit-color-error)] hover:bg-[var(--appkit-color-border-subtle)]", children: _jsx(Trash2, { className: "w-4 h-4" }) }))] }));
|
|
300
|
+
} }))] }), _jsx(SellerProductsFilterDrawer, { isOpen: filterOpen, pendingFilters: pendingFilters, statusOptions: STATUS_OPTIONS, activeFilterCount: activeFilterCount, onChange: setPendingFilters, onClear: clearFilters, onApply: applyFilters, onClose: () => setFilterOpen(false) })] }), setLocationOpen && (_jsx(PhysicalLocationModal, { count: selection.selectedIds.length, onSave: handleSetLocation, onClose: () => setSetLocationOpen(false) }))] }));
|
|
307
301
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface SellerShippingConfigsViewProps {
|
|
2
|
+
onCreateClick?: () => void;
|
|
3
|
+
onEditClick?: (id: string) => void;
|
|
4
|
+
onDelete?: (id: string) => Promise<void>;
|
|
5
|
+
onSetDefault?: (id: string) => Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
export declare function SellerShippingConfigsView({ onCreateClick, onEditClick, onDelete, onSetDefault, }: SellerShippingConfigsViewProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,149 @@
|
|
|
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 { Button, ConfirmDeleteModal, DataTable, Div, ListingToolbar, RowActionMenu, Text, } from "../../../ui";
|
|
7
|
+
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
|
+
import { ROUTES } from "../../..";
|
|
9
|
+
import { toRecordArray, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
|
|
10
|
+
import { TABLE_KEYS } from "../../../constants/table-keys";
|
|
11
|
+
const PAGE_SIZE = 50;
|
|
12
|
+
const DEFAULT_SORT = "-createdAt";
|
|
13
|
+
const SORT_OPTIONS = [
|
|
14
|
+
{ value: "-createdAt", label: "Newest" },
|
|
15
|
+
{ value: "createdAt", label: "Oldest" },
|
|
16
|
+
{ value: "label", label: "Label A–Z" },
|
|
17
|
+
];
|
|
18
|
+
const COLUMNS = [
|
|
19
|
+
{
|
|
20
|
+
key: "label",
|
|
21
|
+
header: "Name",
|
|
22
|
+
render: (row) => (_jsxs(Div, { children: [_jsx(Text, { className: "text-sm font-medium", children: row.label }), row.isDefault && (_jsx("span", { className: "mt-0.5 inline-flex items-center rounded-full px-1.5 py-px text-xs font-medium bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300", children: "Default" }))] })),
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
key: "method",
|
|
26
|
+
header: "Method",
|
|
27
|
+
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.method.replace(/_/g, " ") })),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
key: "estimatedDays",
|
|
31
|
+
header: "Est. Days",
|
|
32
|
+
render: (row) => (_jsx(Text, { className: "text-sm tabular-nums", children: row.estimatedDays !== null ? `${row.estimatedDays}d` : "—" })),
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
key: "isActive",
|
|
36
|
+
header: "Status",
|
|
37
|
+
render: (row) => (_jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${row.isActive
|
|
38
|
+
? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300"
|
|
39
|
+
: "bg-zinc-100 text-zinc-600 dark:bg-slate-800 dark:text-slate-400"}`, children: row.isActive ? "Active" : "Inactive" })),
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
export function SellerShippingConfigsView({ onCreateClick, onEditClick, onDelete, onSetDefault, }) {
|
|
43
|
+
const table = useUrlTable({ defaults: { sort: DEFAULT_SORT } });
|
|
44
|
+
const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
|
|
45
|
+
const [deleteTargetId, setDeleteTargetId] = useState(null);
|
|
46
|
+
const [deletingId, setDeletingId] = useState(null);
|
|
47
|
+
const [settingDefaultId, setSettingDefaultId] = useState(null);
|
|
48
|
+
const commitSearch = useCallback(() => {
|
|
49
|
+
table.set(TABLE_KEYS.QUERY, searchInput.trim());
|
|
50
|
+
}, [searchInput, table]);
|
|
51
|
+
const resetAll = useCallback(() => {
|
|
52
|
+
table.setMany({ [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "" });
|
|
53
|
+
setSearchInput("");
|
|
54
|
+
}, [table]);
|
|
55
|
+
const hasActiveState = !!table.get(TABLE_KEYS.QUERY) || table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT;
|
|
56
|
+
const { rows, isLoading, errorMessage, refetch } = useSellerListingData({
|
|
57
|
+
queryKey: ["seller", "shipping-configs"],
|
|
58
|
+
endpoint: SELLER_ENDPOINTS.SHIPPING_CONFIGS,
|
|
59
|
+
page: 1,
|
|
60
|
+
pageSize: PAGE_SIZE,
|
|
61
|
+
sorts: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT,
|
|
62
|
+
q: table.get(TABLE_KEYS.QUERY) || undefined,
|
|
63
|
+
mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
|
|
64
|
+
id: toStringValue(item.id, `sc-${index}`),
|
|
65
|
+
raw: item,
|
|
66
|
+
label: String(item.label ?? ""),
|
|
67
|
+
method: String(item.method ?? ""),
|
|
68
|
+
estimatedDays: typeof item.estimatedDays === "number" ? item.estimatedDays : null,
|
|
69
|
+
isDefault: Boolean(item.isDefault),
|
|
70
|
+
isActive: Boolean(item.isActive),
|
|
71
|
+
})),
|
|
72
|
+
});
|
|
73
|
+
const handleDelete = useCallback(async (id) => {
|
|
74
|
+
setDeletingId(id);
|
|
75
|
+
try {
|
|
76
|
+
if (onDelete) {
|
|
77
|
+
await onDelete(id);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
await fetch(SELLER_ENDPOINTS.SHIPPING_CONFIG_BY_ID(id), {
|
|
81
|
+
method: "DELETE",
|
|
82
|
+
credentials: "include",
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
refetch?.();
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
setDeletingId(null);
|
|
89
|
+
setDeleteTargetId(null);
|
|
90
|
+
}
|
|
91
|
+
}, [onDelete, refetch]);
|
|
92
|
+
const handleSetDefault = useCallback(async (id) => {
|
|
93
|
+
setSettingDefaultId(id);
|
|
94
|
+
try {
|
|
95
|
+
if (onSetDefault) {
|
|
96
|
+
await onSetDefault(id);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
await fetch(SELLER_ENDPOINTS.SHIPPING_CONFIG_BY_ID(id), {
|
|
100
|
+
method: "PATCH",
|
|
101
|
+
headers: { "Content-Type": "application/json" },
|
|
102
|
+
credentials: "include",
|
|
103
|
+
body: JSON.stringify({ isDefault: true }),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
refetch?.();
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
setSettingDefaultId(null);
|
|
110
|
+
}
|
|
111
|
+
}, [onSetDefault, refetch]);
|
|
112
|
+
const handleCreate = useCallback(() => {
|
|
113
|
+
if (onCreateClick) {
|
|
114
|
+
onCreateClick();
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
window.location.href = String(ROUTES.STORE.SHIPPING_CONFIGS_NEW);
|
|
118
|
+
}
|
|
119
|
+
}, [onCreateClick]);
|
|
120
|
+
const handleEdit = useCallback((id) => {
|
|
121
|
+
if (onEditClick) {
|
|
122
|
+
onEditClick(id);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
window.location.href = String(ROUTES.STORE.SHIPPING_CONFIGS_EDIT(id));
|
|
126
|
+
}
|
|
127
|
+
}, [onEditClick]);
|
|
128
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search shipping configs...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, 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 Config" })] }) }), _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: 3 }).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 ? (_jsxs(Div, { className: "py-16 text-center", children: [_jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: "No shipping configs yet \u2014 define your first shipping rule" }), _jsx("div", { className: "mt-4", children: _jsx(Button, { size: "sm", onClick: handleCreate, children: "Add shipping config" }) })] })) : (_jsx(DataTable, { columns: COLUMNS, data: rows, keyExtractor: (r) => r.id, actions: (row) => (_jsx(RowActionMenu, { actions: [
|
|
129
|
+
{
|
|
130
|
+
label: "Edit",
|
|
131
|
+
onClick: () => handleEdit(row.id),
|
|
132
|
+
},
|
|
133
|
+
...(!row.isDefault
|
|
134
|
+
? [
|
|
135
|
+
{
|
|
136
|
+
label: "Set as Default",
|
|
137
|
+
disabled: settingDefaultId === row.id,
|
|
138
|
+
onClick: () => handleSetDefault(row.id),
|
|
139
|
+
},
|
|
140
|
+
]
|
|
141
|
+
: []),
|
|
142
|
+
{
|
|
143
|
+
label: "Delete",
|
|
144
|
+
destructive: true,
|
|
145
|
+
onClick: () => setDeleteTargetId(row.id),
|
|
146
|
+
disabled: deletingId === row.id,
|
|
147
|
+
},
|
|
148
|
+
] })) }))] }), deleteTargetId && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete Shipping Config", message: "Are you sure you want to delete this shipping configuration? This cannot be undone.", onConfirm: () => handleDelete(deleteTargetId), onClose: () => setDeleteTargetId(null), isDeleting: deletingId === deleteTargetId }))] }));
|
|
149
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback, useEffect, useState } from "react";
|
|
4
|
-
import { Alert, Badge,
|
|
4
|
+
import { Alert, Badge, Div, FormField, Heading, Stack, Text } from "../../../ui";
|
|
5
5
|
import { StackedViewShell } from "../../../ui";
|
|
6
6
|
import { StoreAddressSelectorCreate } from "../../stores/components/StoreAddressSelectorCreate";
|
|
7
|
+
import { StepForm } from "../../shell";
|
|
7
8
|
const DEFAULT_DRAFT = {
|
|
8
9
|
method: "custom",
|
|
9
10
|
customCarrierName: "",
|
|
@@ -11,6 +12,8 @@ const DEFAULT_DRAFT = {
|
|
|
11
12
|
shiprocketEmail: "",
|
|
12
13
|
shiprocketPassword: "",
|
|
13
14
|
pickupAddressId: "",
|
|
15
|
+
freeShippingThreshold: "",
|
|
16
|
+
fragileSurcharge: "",
|
|
14
17
|
};
|
|
15
18
|
export function SellerShippingView({ apiBase = "/api/store/shipping" }) {
|
|
16
19
|
const [draft, setDraft] = useState(DEFAULT_DRAFT);
|
|
@@ -19,6 +22,7 @@ export function SellerShippingView({ apiBase = "/api/store/shipping" }) {
|
|
|
19
22
|
const [saving, setSaving] = useState(false);
|
|
20
23
|
const [error, setError] = useState(null);
|
|
21
24
|
const [success, setSuccess] = useState(false);
|
|
25
|
+
const [currentStep, setCurrentStep] = useState(0);
|
|
22
26
|
useEffect(() => {
|
|
23
27
|
fetch(apiBase)
|
|
24
28
|
.then((r) => r.json())
|
|
@@ -32,13 +36,19 @@ export function SellerShippingView({ apiBase = "/api/store/shipping" }) {
|
|
|
32
36
|
shiprocketEmail: cfg.shiprocketEmail ?? "",
|
|
33
37
|
shiprocketPassword: "",
|
|
34
38
|
pickupAddressId: "",
|
|
39
|
+
freeShippingThreshold: (res?.data?.freeShippingThreshold ?? 0)
|
|
40
|
+
? String(res.data.freeShippingThreshold / 100)
|
|
41
|
+
: "",
|
|
42
|
+
fragileSurcharge: (res?.data?.fragileSurcharge ?? 0)
|
|
43
|
+
? String(res.data.fragileSurcharge / 100)
|
|
44
|
+
: "",
|
|
35
45
|
});
|
|
36
46
|
})
|
|
37
47
|
.catch(() => { })
|
|
38
48
|
.finally(() => setLoading(false));
|
|
39
49
|
}, [apiBase]);
|
|
40
|
-
const update = useCallback((
|
|
41
|
-
setDraft((prev) => ({ ...prev,
|
|
50
|
+
const update = useCallback((partial) => {
|
|
51
|
+
setDraft((prev) => ({ ...prev, ...partial }));
|
|
42
52
|
setSuccess(false);
|
|
43
53
|
}, []);
|
|
44
54
|
const handleSave = async () => {
|
|
@@ -46,7 +56,7 @@ export function SellerShippingView({ apiBase = "/api/store/shipping" }) {
|
|
|
46
56
|
setSuccess(false);
|
|
47
57
|
setSaving(true);
|
|
48
58
|
try {
|
|
49
|
-
const
|
|
59
|
+
const methodFields = draft.method === "custom"
|
|
50
60
|
? {
|
|
51
61
|
method: "custom",
|
|
52
62
|
customCarrierName: draft.customCarrierName.trim(),
|
|
@@ -58,6 +68,12 @@ export function SellerShippingView({ apiBase = "/api/store/shipping" }) {
|
|
|
58
68
|
? { shiprocketCredentials: { email: draft.shiprocketEmail, password: draft.shiprocketPassword } }
|
|
59
69
|
: {}),
|
|
60
70
|
};
|
|
71
|
+
const body = {
|
|
72
|
+
...methodFields,
|
|
73
|
+
...(draft.pickupAddressId ? { pickupAddressId: draft.pickupAddressId } : {}),
|
|
74
|
+
freeShippingThreshold: Math.round(parseFloat(draft.freeShippingThreshold || "0") * 100),
|
|
75
|
+
fragileSurcharge: Math.round(parseFloat(draft.fragileSurcharge || "0") * 100),
|
|
76
|
+
};
|
|
61
77
|
const res = await fetch(apiBase, {
|
|
62
78
|
method: "PATCH",
|
|
63
79
|
headers: { "Content-Type": "application/json" },
|
|
@@ -77,13 +93,24 @@ export function SellerShippingView({ apiBase = "/api/store/shipping" }) {
|
|
|
77
93
|
setSaving(false);
|
|
78
94
|
}
|
|
79
95
|
};
|
|
80
|
-
const isCustom = draft.method === "custom";
|
|
81
96
|
const busy = loading || saving;
|
|
97
|
+
const steps = [
|
|
98
|
+
{
|
|
99
|
+
label: "Method",
|
|
100
|
+
render: ({ values, onChange }) => (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Shipping Method" }), current && (_jsxs(Div, { className: "flex items-center gap-2 mb-2", children: [_jsx(Badge, { variant: current.isConfigured ? "success" : "warning", children: current.isConfigured ? "Configured" : "Not configured" }), current.method === "shiprocket" && (_jsx(Badge, { variant: current.isTokenValid ? "success" : "danger", children: current.isTokenValid ? "Shiprocket connected" : "Shiprocket token expired" }))] })), _jsx(Stack, { gap: "sm", children: ["custom", "shiprocket"].map((m) => (_jsxs("label", { className: "flex items-center gap-3 p-3 rounded-lg border border-[var(--appkit-color-border)] cursor-pointer has-[:checked]:border-[var(--appkit-color-primary)] has-[:checked]:bg-[var(--appkit-color-primary)]/5", children: [_jsx("input", { type: "radio", name: "method", value: m, checked: values.method === m, onChange: () => onChange({ method: m }), className: "accent-[var(--appkit-color-primary)]", disabled: busy }), _jsxs(Div, { children: [_jsx(Text, { className: "font-medium", children: m === "custom" ? "Custom / Manual" : "Shiprocket" }), _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: m === "custom"
|
|
101
|
+
? "Set a fixed shipping fee and carrier name for all orders."
|
|
102
|
+
: "Automated shipping via Shiprocket — connect your account for label generation and tracking." })] })] }, m))) }), values.method === "custom" && (_jsxs(Stack, { gap: "md", className: "mt-2", children: [_jsx(FormField, { name: "customCarrierName", label: "Carrier Name", type: "text", value: values.customCarrierName, onChange: (v) => onChange({ customCarrierName: v }), placeholder: "e.g. India Post, DTDC, Delhivery", disabled: busy }), _jsx(FormField, { name: "customShippingPrice", label: "Shipping Price (\u20B9)", type: "number", value: values.customShippingPrice, onChange: (v) => onChange({ customShippingPrice: v }), placeholder: "0 for free shipping", helpText: "Charged to buyer at checkout. Enter 0 for free shipping.", disabled: busy })] })), values.method === "shiprocket" && (_jsxs(Stack, { gap: "md", className: "mt-2", children: [_jsx(FormField, { name: "shiprocketEmail", label: "Shiprocket Email", type: "email", value: values.shiprocketEmail, onChange: (v) => onChange({ shiprocketEmail: v }), placeholder: "your@email.com", disabled: busy }), _jsx(FormField, { name: "shiprocketPassword", label: current?.isTokenValid ? "Password (leave blank to keep existing token)" : "Password", type: "password", value: values.shiprocketPassword, onChange: (v) => onChange({ shiprocketPassword: v }), placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", helpText: current?.isTokenValid ? "Only fill this to re-authenticate." : "Required to connect your Shiprocket account.", disabled: busy })] }))] })),
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
label: "Pickup Address",
|
|
106
|
+
render: ({ values, onChange }) => (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Pickup Address" }), current?.pickupAddress && (_jsxs(Alert, { variant: "info", children: ["Current pickup: ", current.pickupAddress.locationName ?? "", current.pickupAddress.city ? `, ${current.pickupAddress.city}` : "", current.pickupAddress.isVerified ? " — ✓ Verified" : " — Pending OTP verification"] })), _jsx(StoreAddressSelectorCreate, { value: values.pickupAddressId, onChange: (id) => onChange({ pickupAddressId: id }), label: "Pickup Address", disabled: busy }), _jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)]", children: "Registering a pickup address sends an OTP to your phone for verification." })] })),
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
label: "Rules",
|
|
110
|
+
render: ({ values, onChange }) => (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Shipping Rules" }), _jsx(FormField, { name: "freeShippingThreshold", label: "Free Shipping Threshold (\u20B9)", type: "number", value: values.freeShippingThreshold, onChange: (v) => onChange({ freeShippingThreshold: v }), placeholder: "e.g. 500 \u2014 orders above this get free shipping", helpText: "Leave blank or 0 to disable free shipping offers.", disabled: busy }), _jsx(FormField, { name: "fragileSurcharge", label: "Fragile Item Surcharge (\u20B9)", type: "number", value: values.fragileSurcharge, onChange: (v) => onChange({ fragileSurcharge: v }), placeholder: "e.g. 50 \u2014 added for items marked fragile", helpText: "Leave blank or 0 to disable the fragile surcharge.", disabled: busy })] })),
|
|
111
|
+
},
|
|
112
|
+
];
|
|
82
113
|
return (_jsx(StackedViewShell, { portal: "seller", title: "Shipping Configuration", sections: [
|
|
83
|
-
_jsxs(
|
|
84
|
-
? "Set a fixed shipping fee and carrier name for all orders."
|
|
85
|
-
: "Automated shipping via Shiprocket — connect your account for label generation and tracking." })] })] }, m))) })] }), isCustom && (_jsxs(Section, { children: [_jsx(Heading, { level: 3, className: "mb-3", children: "Custom Shipping Details" }), _jsxs(Stack, { gap: "md", children: [_jsx(FormField, { name: "customCarrierName", label: "Carrier Name", type: "text", value: draft.customCarrierName, onChange: (v) => update("customCarrierName", v), placeholder: "e.g. India Post, DTDC, Delhivery", disabled: busy }), _jsx(FormField, { name: "customShippingPrice", label: "Shipping Price (\u20B9)", type: "number", value: draft.customShippingPrice, onChange: (v) => update("customShippingPrice", v), placeholder: "0 for free shipping", helpText: "Charged to buyer at checkout. Enter 0 for free shipping.", disabled: busy })] })] })), !isCustom && (_jsxs(Section, { children: [_jsx(Heading, { level: 3, className: "mb-3", children: "Shiprocket Account" }), _jsxs(Stack, { gap: "md", children: [_jsx(FormField, { name: "shiprocketEmail", label: "Shiprocket Email", type: "email", value: draft.shiprocketEmail, onChange: (v) => update("shiprocketEmail", v), placeholder: "your@email.com", disabled: busy }), _jsx(FormField, { name: "shiprocketPassword", label: current?.isTokenValid ? "Password (leave blank to keep existing token)" : "Password", type: "password", value: draft.shiprocketPassword, onChange: (v) => update("shiprocketPassword", v), placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", helpText: current?.isTokenValid ? "Only fill this to re-authenticate." : "Required to connect your Shiprocket account.", disabled: busy }), _jsxs(Div, { children: [_jsx(StoreAddressSelectorCreate, { value: draft.pickupAddressId, onChange: (id) => update("pickupAddressId", id), label: "Pickup Address (optional)", disabled: busy }), _jsx(Text, { className: "mt-1 text-xs text-[var(--appkit-color-text-muted)]", children: "Registering a pickup address sends an OTP to your phone for verification." })] }), current?.pickupAddress && (_jsxs(Alert, { variant: "info", children: ["Current pickup: ", current.pickupAddress.locationName ?? "", current.pickupAddress.city ? `, ${current.pickupAddress.city}` : "", current.pickupAddress.isVerified
|
|
86
|
-
? " — ✓ Verified"
|
|
87
|
-
: " — Pending OTP verification"] }))] })] })), _jsx(Div, { className: "flex justify-end pt-2 border-t border-[var(--appkit-color-border)]", children: _jsx(Button, { variant: "primary", onClick: handleSave, disabled: busy, isLoading: saving, children: "Save Configuration" }) })] }, "shipping"),
|
|
114
|
+
_jsxs("div", { children: [error && _jsx(Alert, { variant: "error", className: "mb-4", children: error }), success && _jsx(Alert, { variant: "success", className: "mb-4", children: "Shipping configuration saved." }), _jsx(StepForm, { steps: steps, values: draft, onChange: update, onComplete: handleSave, formId: "seller-shipping", currentStep: currentStep, onStepChange: setCurrentStep, completeLabel: "Save Configuration", isLoading: busy })] }, "shipping"),
|
|
88
115
|
] }));
|
|
89
116
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface SellerStoreCategoriesViewProps {
|
|
2
|
+
onCreateClick?: () => void;
|
|
3
|
+
onEditClick?: (id: string) => void;
|
|
4
|
+
onDelete?: (id: string) => Promise<void>;
|
|
5
|
+
onBulkDelete?: (ids: string[]) => Promise<void>;
|
|
6
|
+
onBulkActivate?: (ids: string[]) => Promise<void>;
|
|
7
|
+
onBulkDeactivate?: (ids: string[]) => Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export declare function SellerStoreCategoriesView({ onCreateClick, onEditClick, onDelete, onBulkDelete, onBulkActivate, onBulkDeactivate, }: SellerStoreCategoriesViewProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,122 @@
|
|
|
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, ConfirmDeleteModal, DataTable, Div, ListingToolbar, Pagination, RowActionMenu, Text, } from "../../../ui";
|
|
8
|
+
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
|
+
import { ROUTES } from "../../../next";
|
|
10
|
+
import { toRecordArray, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
|
|
11
|
+
import { TABLE_KEYS } from "../../../constants/table-keys";
|
|
12
|
+
const PAGE_SIZE = 25;
|
|
13
|
+
const DEFAULT_SORT = "displayOrder";
|
|
14
|
+
const SORT_OPTIONS = [
|
|
15
|
+
{ value: "displayOrder", label: "Display Order" },
|
|
16
|
+
{ value: "label", label: "Label A–Z" },
|
|
17
|
+
{ value: "-label", label: "Label Z–A" },
|
|
18
|
+
{ value: "-createdAt", label: "Newest" },
|
|
19
|
+
];
|
|
20
|
+
const COLUMNS = [
|
|
21
|
+
{
|
|
22
|
+
key: "label",
|
|
23
|
+
header: "Label",
|
|
24
|
+
render: (row) => (_jsxs(Div, { children: [_jsx(Text, { className: "text-sm font-medium", children: row.label }), _jsxs(Text, { className: "text-xs text-[var(--appkit-color-text-muted)]", children: ["/", row.slug] })] })),
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
key: "productCount",
|
|
28
|
+
header: "Products",
|
|
29
|
+
render: (row) => _jsx(Text, { className: "text-sm tabular-nums", children: row.productCount }),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
key: "isActive",
|
|
33
|
+
header: "Status",
|
|
34
|
+
render: (row) => (_jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${row.isActive
|
|
35
|
+
? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300"
|
|
36
|
+
: "bg-zinc-100 text-zinc-600 dark:bg-slate-800 dark:text-slate-400"}`, children: row.isActive ? "Active" : "Hidden" })),
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
export function SellerStoreCategoriesView({ onCreateClick, onEditClick, onDelete, onBulkDelete, onBulkActivate, onBulkDeactivate, }) {
|
|
40
|
+
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
41
|
+
const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
|
|
42
|
+
const [deleteTargetId, setDeleteTargetId] = useState(null);
|
|
43
|
+
const [deletingId, setDeletingId] = useState(null);
|
|
44
|
+
const commitSearch = useCallback(() => { table.set(TABLE_KEYS.QUERY, searchInput.trim()); }, [searchInput, table]);
|
|
45
|
+
const resetAll = useCallback(() => {
|
|
46
|
+
table.setMany({ [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "" });
|
|
47
|
+
setSearchInput("");
|
|
48
|
+
}, [table]);
|
|
49
|
+
const hasActiveState = !!table.get(TABLE_KEYS.QUERY) || table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT;
|
|
50
|
+
const { rows, total, isLoading, errorMessage, refetch } = useSellerListingData({
|
|
51
|
+
queryKey: ["seller", "store-categories"],
|
|
52
|
+
endpoint: SELLER_ENDPOINTS.STORE_CATEGORIES,
|
|
53
|
+
page: table.getNumber(TABLE_KEYS.PAGE, 1),
|
|
54
|
+
pageSize: PAGE_SIZE,
|
|
55
|
+
sorts: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT,
|
|
56
|
+
q: table.get(TABLE_KEYS.QUERY) || undefined,
|
|
57
|
+
mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
|
|
58
|
+
id: toStringValue(item.id, `cat-${index}`),
|
|
59
|
+
raw: item,
|
|
60
|
+
label: String(item.label ?? ""),
|
|
61
|
+
slug: String(item.slug ?? ""),
|
|
62
|
+
productCount: Number(item.productIds?.length ?? 0),
|
|
63
|
+
isActive: Boolean(item.isActive),
|
|
64
|
+
})),
|
|
65
|
+
getTotal: (response, rows) => typeof response.total === "number" ? response.total : rows.length,
|
|
66
|
+
});
|
|
67
|
+
const currentPage = table.getNumber(TABLE_KEYS.PAGE, 1);
|
|
68
|
+
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
69
|
+
const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
|
|
70
|
+
const handleDelete = useCallback(async (id) => {
|
|
71
|
+
if (!onDelete)
|
|
72
|
+
return;
|
|
73
|
+
setDeletingId(id);
|
|
74
|
+
try {
|
|
75
|
+
await onDelete(id);
|
|
76
|
+
refetch?.();
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
setDeletingId(null);
|
|
80
|
+
setDeleteTargetId(null);
|
|
81
|
+
}
|
|
82
|
+
}, [onDelete, refetch]);
|
|
83
|
+
const bulkActions = [
|
|
84
|
+
...(onBulkDelete ? [{
|
|
85
|
+
id: "bulk-delete",
|
|
86
|
+
label: "Delete selected",
|
|
87
|
+
variant: "danger",
|
|
88
|
+
onClick: async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); refetch?.(); },
|
|
89
|
+
}] : []),
|
|
90
|
+
...(onBulkActivate ? [{
|
|
91
|
+
id: "bulk-activate",
|
|
92
|
+
label: "Activate",
|
|
93
|
+
onClick: async () => { await onBulkActivate(selection.selectedIds); selection.clearSelection(); refetch?.(); },
|
|
94
|
+
}] : []),
|
|
95
|
+
...(onBulkDeactivate ? [{
|
|
96
|
+
id: "bulk-deactivate",
|
|
97
|
+
label: "Deactivate",
|
|
98
|
+
onClick: async () => { await onBulkDeactivate(selection.selectedIds); selection.clearSelection(); refetch?.(); },
|
|
99
|
+
}] : []),
|
|
100
|
+
];
|
|
101
|
+
const handleNavigateNew = () => {
|
|
102
|
+
if (onCreateClick) {
|
|
103
|
+
onCreateClick();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
window.location.href = String(ROUTES.STORE.STORE_CATEGORIES_NEW);
|
|
107
|
+
};
|
|
108
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search categories by label...", 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: "table", onViewChange: () => { }, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: handleNavigateNew, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: "New Category" })] }) }), 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 && (_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 categories yet \u2014 add your first storefront category" }) })) : (_jsx(DataTable, { columns: COLUMNS, data: rows, keyExtractor: (r) => r.id, selectable: bulkActions.length > 0, selectedIds: selection.selectedIds, onSelectionChange: (ids) => selection.setSelectedIds(ids), actions: (row) => (_jsx(RowActionMenu, { actions: [
|
|
109
|
+
{
|
|
110
|
+
label: "Edit",
|
|
111
|
+
onClick: () => onEditClick
|
|
112
|
+
? onEditClick(row.id)
|
|
113
|
+
: (window.location.href = String(ROUTES.STORE.STORE_CATEGORIES_EDIT(row.id))),
|
|
114
|
+
},
|
|
115
|
+
...(onDelete ? [{
|
|
116
|
+
label: "Delete",
|
|
117
|
+
destructive: true,
|
|
118
|
+
onClick: () => setDeleteTargetId(row.id),
|
|
119
|
+
disabled: deletingId === row.id,
|
|
120
|
+
}] : []),
|
|
121
|
+
] })) }))] }), deleteTargetId && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete Category", message: "Are you sure you want to delete this category? This cannot be undone.", onConfirm: () => handleDelete(deleteTargetId), onClose: () => setDeleteTargetId(null), isDeleting: deletingId === deleteTargetId }))] }));
|
|
122
|
+
}
|