@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.
Files changed (143) hide show
  1. package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.d.ts +1 -0
  2. package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.js +65 -0
  3. package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.d.ts +1 -0
  4. package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.js +163 -0
  5. package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.d.ts +1 -0
  6. package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.js +115 -0
  7. package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.d.ts +1 -0
  8. package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.js +96 -0
  9. package/dist/client.d.ts +29 -6
  10. package/dist/client.js +16 -4
  11. package/dist/constants/api-endpoints.d.ts +42 -0
  12. package/dist/constants/api-endpoints.js +14 -0
  13. package/dist/features/_guide-cls.d.ts +13 -0
  14. package/dist/features/_guide-cls.js +14 -0
  15. package/dist/features/about/components/HelpPageView.js +29 -26
  16. package/dist/features/account/components/BuyerAccountGuideView.d.ts +1 -0
  17. package/dist/features/account/components/BuyerAccountGuideView.js +39 -0
  18. package/dist/features/account/components/BuyerAuctionsGuideView.d.ts +1 -0
  19. package/dist/features/account/components/BuyerAuctionsGuideView.js +38 -0
  20. package/dist/features/account/components/BuyerOrdersGuideView.d.ts +1 -0
  21. package/dist/features/account/components/BuyerOrdersGuideView.js +48 -0
  22. package/dist/features/account/components/BuyerShoppingGuideView.d.ts +1 -0
  23. package/dist/features/account/components/BuyerShoppingGuideView.js +38 -0
  24. package/dist/features/admin/components/AdminAnalyticsGuideView.d.ts +1 -0
  25. package/dist/features/admin/components/AdminAnalyticsGuideView.js +24 -0
  26. package/dist/features/admin/components/AdminBlogEditorView.js +100 -77
  27. package/dist/features/admin/components/AdminBundleEditorView.js +20 -14
  28. package/dist/features/admin/components/AdminBundlesView.d.ts +1 -3
  29. package/dist/features/admin/components/AdminBundlesView.js +173 -52
  30. package/dist/features/admin/components/AdminCatalogGuideView.d.ts +1 -0
  31. package/dist/features/admin/components/AdminCatalogGuideView.js +28 -0
  32. package/dist/features/admin/components/AdminContentGuideView.d.ts +1 -0
  33. package/dist/features/admin/components/AdminContentGuideView.js +36 -0
  34. package/dist/features/admin/components/AdminCouponEditorView.js +1 -1
  35. package/dist/features/admin/components/AdminGuideHubView.d.ts +5 -0
  36. package/dist/features/admin/components/AdminGuideHubView.js +79 -0
  37. package/dist/features/admin/components/AdminOrdersGuideView.d.ts +1 -0
  38. package/dist/features/admin/components/AdminOrdersGuideView.js +44 -0
  39. package/dist/features/admin/components/AdminProductsView.js +8 -3
  40. package/dist/features/admin/components/AdminSiteConfigGuideView.d.ts +1 -0
  41. package/dist/features/admin/components/AdminSiteConfigGuideView.js +21 -0
  42. package/dist/features/admin/components/AdminStoresGuideView.d.ts +1 -0
  43. package/dist/features/admin/components/AdminStoresGuideView.js +32 -0
  44. package/dist/features/admin/components/AdminTeamGuideView.d.ts +1 -0
  45. package/dist/features/admin/components/AdminTeamGuideView.js +33 -0
  46. package/dist/features/admin/components/AdminTrustGuideView.d.ts +1 -0
  47. package/dist/features/admin/components/AdminTrustGuideView.js +40 -0
  48. package/dist/features/admin/components/AdminUsersGuideView.d.ts +1 -0
  49. package/dist/features/admin/components/AdminUsersGuideView.js +38 -0
  50. package/dist/features/categories/components/CategoryBundlesListing.js +3 -33
  51. package/dist/features/categories/components/index.d.ts +0 -2
  52. package/dist/features/categories/components/index.js +0 -1
  53. package/dist/features/events/components/AdminEventEditorView.js +223 -269
  54. package/dist/features/grouped/repository/grouped-listings.repository.d.ts +9 -0
  55. package/dist/features/grouped/repository/grouped-listings.repository.js +12 -0
  56. package/dist/features/media/upload/MediaUploadField.js +1 -1
  57. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +3 -1
  58. package/dist/features/products/components/AuctionsIndexListing.js +3 -1
  59. package/dist/features/products/components/MarketplaceBundleCard.d.ts +21 -0
  60. package/dist/features/products/components/MarketplaceBundleCard.js +56 -0
  61. package/dist/features/products/components/ProductsIndexListing.js +3 -1
  62. package/dist/features/products/components/index.d.ts +2 -4
  63. package/dist/features/products/components/index.js +1 -2
  64. package/dist/features/promotions/schemas/firestore.js +1 -1
  65. package/dist/features/reviews/components/index.d.ts +0 -2
  66. package/dist/features/reviews/components/index.js +0 -1
  67. package/dist/features/seller/components/BrandInlineSelect.js +23 -3
  68. package/dist/features/seller/components/CategoryInlineSelect.js +20 -3
  69. package/dist/features/seller/components/SellerAnalyticsAlertsView.d.ts +6 -0
  70. package/dist/features/seller/components/SellerAnalyticsAlertsView.js +124 -0
  71. package/dist/features/seller/components/SellerAnalyticsView.d.ts +2 -0
  72. package/dist/features/seller/components/SellerAnalyticsView.js +17 -9
  73. package/dist/features/seller/components/SellerBundlesView.d.ts +5 -0
  74. package/dist/features/seller/components/SellerBundlesView.js +104 -0
  75. package/dist/features/seller/components/SellerClassifiedView.d.ts +5 -0
  76. package/dist/features/seller/components/SellerClassifiedView.js +113 -0
  77. package/dist/features/seller/components/SellerDashboardView.js +19 -7
  78. package/dist/features/seller/components/SellerDigitalCodesView.d.ts +5 -0
  79. package/dist/features/seller/components/SellerDigitalCodesView.js +115 -0
  80. package/dist/features/seller/components/SellerGoogleReviewsView.d.ts +14 -0
  81. package/dist/features/seller/components/SellerGoogleReviewsView.js +95 -0
  82. package/dist/features/seller/components/SellerGroupedListingsView.d.ts +6 -0
  83. package/dist/features/seller/components/SellerGroupedListingsView.js +112 -0
  84. package/dist/features/seller/components/SellerLiveView.d.ts +5 -0
  85. package/dist/features/seller/components/SellerLiveView.js +113 -0
  86. package/dist/features/seller/components/SellerOrdersView.js +18 -2
  87. package/dist/features/seller/components/SellerPayoutMethodsView.d.ts +7 -0
  88. package/dist/features/seller/components/SellerPayoutMethodsView.js +120 -0
  89. package/dist/features/seller/components/SellerPayoutSettingsView.js +50 -12
  90. package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
  91. package/dist/features/seller/components/SellerProductShell.js +9 -7
  92. package/dist/features/seller/components/SellerProductsFilterDrawer.js +3 -1
  93. package/dist/features/seller/components/SellerProductsView.js +48 -54
  94. package/dist/features/seller/components/SellerShippingConfigsView.d.ts +7 -0
  95. package/dist/features/seller/components/SellerShippingConfigsView.js +149 -0
  96. package/dist/features/seller/components/SellerShippingView.js +37 -10
  97. package/dist/features/seller/components/SellerStoreCategoriesView.d.ts +9 -0
  98. package/dist/features/seller/components/SellerStoreCategoriesView.js +122 -0
  99. package/dist/features/seller/components/SellerStorefrontView.d.ts +0 -7
  100. package/dist/features/seller/components/SellerStorefrontView.js +26 -29
  101. package/dist/features/seller/components/SellerTemplatesView.d.ts +10 -0
  102. package/dist/features/seller/components/SellerTemplatesView.js +265 -0
  103. package/dist/features/seller/components/index.d.ts +26 -0
  104. package/dist/features/seller/components/index.js +13 -0
  105. package/dist/features/stores/api/[storeSlug]/reviews/route.js +49 -7
  106. package/dist/features/stores/components/StoreCapabilitiesGuideView.d.ts +3 -0
  107. package/dist/features/stores/components/StoreCapabilitiesGuideView.js +101 -0
  108. package/dist/features/stores/components/StoreFinanceGuideView.d.ts +3 -0
  109. package/dist/features/stores/components/StoreFinanceGuideView.js +79 -0
  110. package/dist/features/stores/components/StoreGuideHubView.d.ts +5 -0
  111. package/dist/features/stores/components/StoreGuideHubView.js +89 -0
  112. package/dist/features/stores/components/StoreListingsGuideView.d.ts +3 -0
  113. package/dist/features/stores/components/StoreListingsGuideView.js +151 -0
  114. package/dist/features/stores/components/StoreOrdersGuideView.d.ts +3 -0
  115. package/dist/features/stores/components/StoreOrdersGuideView.js +122 -0
  116. package/dist/features/stores/components/StoreReviewsListing.js +100 -23
  117. package/dist/features/stores/components/StoreSettingsGuideView.d.ts +3 -0
  118. package/dist/features/stores/components/StoreSettingsGuideView.js +56 -0
  119. package/dist/features/stores/components/index.d.ts +12 -1
  120. package/dist/features/stores/components/index.js +6 -1
  121. package/dist/features/stores/hooks/useStores.d.ts +5 -0
  122. package/dist/features/stores/hooks/useStores.js +10 -0
  123. package/dist/features/stores/manifest.js +1 -1
  124. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +13 -2
  125. package/dist/index.d.ts +54 -9
  126. package/dist/index.js +58 -15
  127. package/dist/next/routing/route-map.d.ts +55 -0
  128. package/dist/next/routing/route-map.js +25 -0
  129. package/dist/providers/db-firebase/__tests__/filter-aliases.test.d.ts +1 -0
  130. package/dist/providers/db-firebase/__tests__/filter-aliases.test.js +93 -0
  131. package/dist/providers/db-firebase/sieve.d.ts +49 -0
  132. package/dist/providers/db-firebase/sieve.js +61 -8
  133. package/dist/repositories/index.d.ts +1 -0
  134. package/dist/repositories/index.js +2 -0
  135. package/dist/tailwind-utilities.css +1 -1
  136. package/dist/ui/components/ListingToolbar.d.ts +8 -1
  137. package/dist/ui/components/ListingToolbar.js +4 -2
  138. package/dist/ui/components/__tests__/BulkActionBar.test.d.ts +1 -0
  139. package/dist/ui/components/__tests__/BulkActionBar.test.js +96 -0
  140. package/dist/ui/components/__tests__/ListingToolbar.test.d.ts +1 -0
  141. package/dist/ui/components/__tests__/ListingToolbar.test.js +125 -0
  142. package/dist/ui/index.d.ts +1 -1
  143. 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, } from "./seller-products-styles";
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
- const [listingKind, setListingKind] = useState("all");
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
- setListingKind(kind);
161
- table.set("page", "1");
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 === "auction"
173
- ? "listingType==auction"
174
- : listingKind === "pre-order"
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 selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
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, 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: rows, 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: rows, isLoading: isLoading, emptyLabel: listingKind !== "all"
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: (next) => selection.toggleAll(), getRowHref: (row) =>
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: onDeleteProduct
305
- ? (row) => (_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 handleDuplicate(row); }, "aria-label": "Duplicate listing", title: "Duplicate", children: "\u29C9" }), _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" }) })] }))
306
- : undefined }))] }), _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) }))] }));
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, Button, Div, FormField, Heading, Section, Stack, Text } from "../../../ui";
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((key, value) => {
41
- setDraft((prev) => ({ ...prev, [key]: value }));
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 body = draft.method === "custom"
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(Stack, { gap: "lg", children: [current && (_jsxs(Div, { className: "flex items-center gap-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" }))] })), error && _jsx(Alert, { variant: "error", children: error }), success && _jsx(Alert, { variant: "success", children: "Shipping configuration saved." }), _jsxs(Section, { children: [_jsx(Heading, { level: 3, className: "mb-3", children: "Shipping Method" }), _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: draft.method === m, onChange: () => update("method", m), className: "accent-[var(--appkit-color-primary)]", disabled: busy }), _jsxs(Div, { children: [_jsx(Text, { className: "font-medium capitalize", children: m === "custom" ? "Custom / Manual" : "Shiprocket" }), _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: m === "custom"
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
+ }