@mohasinac/appkit 2.7.49 → 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.
Files changed (232) hide show
  1. package/dist/_internal/client/features/layout/SidebarCollapseToggle.d.ts +19 -0
  2. package/dist/_internal/client/features/layout/SidebarCollapseToggle.js +15 -0
  3. package/dist/_internal/client/features/layout/index.d.ts +2 -0
  4. package/dist/_internal/client/features/layout/index.js +1 -0
  5. package/dist/_internal/server/features/checkout/actions.js +25 -2
  6. package/dist/_internal/server/features/promotions/actions.js +3 -1
  7. package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.d.ts +1 -0
  8. package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.js +65 -0
  9. package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.d.ts +1 -0
  10. package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.js +163 -0
  11. package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.d.ts +1 -0
  12. package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.js +115 -0
  13. package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.d.ts +1 -0
  14. package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.js +96 -0
  15. package/dist/_internal/shared/actions/action-registry.js +149 -0
  16. package/dist/client.d.ts +45 -6
  17. package/dist/client.js +27 -4
  18. package/dist/constants/api-endpoints.d.ts +42 -0
  19. package/dist/constants/api-endpoints.js +14 -0
  20. package/dist/features/_guide-cls.d.ts +13 -0
  21. package/dist/features/_guide-cls.js +14 -0
  22. package/dist/features/about/components/HelpPageView.js +29 -26
  23. package/dist/features/account/components/BuyerAccountGuideView.d.ts +1 -0
  24. package/dist/features/account/components/BuyerAccountGuideView.js +39 -0
  25. package/dist/features/account/components/BuyerAuctionsGuideView.d.ts +1 -0
  26. package/dist/features/account/components/BuyerAuctionsGuideView.js +38 -0
  27. package/dist/features/account/components/BuyerOrdersGuideView.d.ts +1 -0
  28. package/dist/features/account/components/BuyerOrdersGuideView.js +48 -0
  29. package/dist/features/account/components/BuyerShoppingGuideView.d.ts +1 -0
  30. package/dist/features/account/components/BuyerShoppingGuideView.js +38 -0
  31. package/dist/features/account/components/UserSidebar.js +2 -1
  32. package/dist/features/admin/components/AdminAnalyticsGuideView.d.ts +1 -0
  33. package/dist/features/admin/components/AdminAnalyticsGuideView.js +24 -0
  34. package/dist/features/admin/components/AdminBlogEditorView.js +102 -75
  35. package/dist/features/admin/components/AdminBundleEditorView.js +20 -14
  36. package/dist/features/admin/components/AdminBundlesView.d.ts +1 -3
  37. package/dist/features/admin/components/AdminBundlesView.js +173 -52
  38. package/dist/features/admin/components/AdminCatalogGuideView.d.ts +1 -0
  39. package/dist/features/admin/components/AdminCatalogGuideView.js +28 -0
  40. package/dist/features/admin/components/AdminContentGuideView.d.ts +1 -0
  41. package/dist/features/admin/components/AdminContentGuideView.js +36 -0
  42. package/dist/features/admin/components/AdminCouponEditorView.js +1 -1
  43. package/dist/features/admin/components/AdminGuideHubView.d.ts +5 -0
  44. package/dist/features/admin/components/AdminGuideHubView.js +79 -0
  45. package/dist/features/admin/components/AdminOrdersGuideView.d.ts +1 -0
  46. package/dist/features/admin/components/AdminOrdersGuideView.js +44 -0
  47. package/dist/features/admin/components/AdminProductsView.js +8 -3
  48. package/dist/features/admin/components/AdminSidebar.js +2 -1
  49. package/dist/features/admin/components/AdminSiteConfigGuideView.d.ts +1 -0
  50. package/dist/features/admin/components/AdminSiteConfigGuideView.js +21 -0
  51. package/dist/features/admin/components/AdminStoresGuideView.d.ts +1 -0
  52. package/dist/features/admin/components/AdminStoresGuideView.js +32 -0
  53. package/dist/features/admin/components/AdminTeamGuideView.d.ts +1 -0
  54. package/dist/features/admin/components/AdminTeamGuideView.js +33 -0
  55. package/dist/features/admin/components/AdminTrustGuideView.d.ts +1 -0
  56. package/dist/features/admin/components/AdminTrustGuideView.js +40 -0
  57. package/dist/features/admin/components/AdminUsersGuideView.d.ts +1 -0
  58. package/dist/features/admin/components/AdminUsersGuideView.js +38 -0
  59. package/dist/features/auth/repository/session.repository.js +16 -1
  60. package/dist/features/cart/hooks/useAddToCart.d.ts +2 -0
  61. package/dist/features/cart/hooks/useAddToCart.js +2 -0
  62. package/dist/features/cart/hooks/useGuestCart.d.ts +2 -0
  63. package/dist/features/cart/utils/guest-cart.d.ts +7 -0
  64. package/dist/features/cart/utils/pending-ops.d.ts +6 -0
  65. package/dist/features/categories/components/CategoryBundlesListing.js +3 -25
  66. package/dist/features/categories/components/CategoryProductsListing.js +6 -10
  67. package/dist/features/categories/components/index.d.ts +0 -2
  68. package/dist/features/categories/components/index.js +0 -1
  69. package/dist/features/categories/schemas/firestore.d.ts +9 -0
  70. package/dist/features/events/components/AdminEventEditorView.js +223 -269
  71. package/dist/features/events/components/EventRaffleWinnerView.d.ts +9 -1
  72. package/dist/features/events/components/EventRaffleWinnerView.js +3 -3
  73. package/dist/features/events/components/SpinWheelView.js +3 -3
  74. package/dist/features/grouped/repository/grouped-listings.repository.d.ts +9 -0
  75. package/dist/features/grouped/repository/grouped-listings.repository.js +12 -0
  76. package/dist/features/layout/BottomNavLayout.d.ts +5 -0
  77. package/dist/features/layout/BottomNavLayout.js +21 -0
  78. package/dist/features/layout/TitleBar.js +7 -1
  79. package/dist/features/layout/TitleBarLayout.d.ts +8 -2
  80. package/dist/features/layout/TitleBarLayout.js +11 -7
  81. package/dist/features/media/upload/MediaUploadField.js +1 -1
  82. package/dist/features/orders/schemas/index.d.ts +2 -2
  83. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +12 -6
  84. package/dist/features/products/components/AuctionsIndexListing.js +3 -1
  85. package/dist/features/products/components/MarketplaceBundleCard.d.ts +21 -0
  86. package/dist/features/products/components/MarketplaceBundleCard.js +56 -0
  87. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  88. package/dist/features/products/components/PrizeDrawEntryActions.d.ts +3 -1
  89. package/dist/features/products/components/PrizeDrawEntryActions.js +8 -12
  90. package/dist/features/products/components/ProductDetailActions.d.ts +5 -1
  91. package/dist/features/products/components/ProductDetailActions.js +3 -1
  92. package/dist/features/products/components/ProductDetailPageView.d.ts +2 -0
  93. package/dist/features/products/components/ProductDetailPageView.js +4 -0
  94. package/dist/features/products/components/ProductGrid.js +3 -3
  95. package/dist/features/products/components/ProductsIndexListing.js +18 -9
  96. package/dist/features/products/components/index.d.ts +2 -4
  97. package/dist/features/products/components/index.js +1 -2
  98. package/dist/features/promotions/components/CouponCard.d.ts +11 -1
  99. package/dist/features/promotions/components/CouponCard.js +36 -2
  100. package/dist/features/promotions/repository/claimed-coupons.repository.d.ts +27 -0
  101. package/dist/features/promotions/repository/claimed-coupons.repository.js +115 -0
  102. package/dist/features/promotions/schemas/firestore.d.ts +34 -0
  103. package/dist/features/promotions/schemas/firestore.js +14 -1
  104. package/dist/features/reviews/components/index.d.ts +0 -2
  105. package/dist/features/reviews/components/index.js +0 -1
  106. package/dist/features/seller/components/BrandInlineSelect.js +23 -3
  107. package/dist/features/seller/components/CategoryInlineSelect.js +20 -3
  108. package/dist/features/seller/components/SellerAddressesView.js +3 -2
  109. package/dist/features/seller/components/SellerAnalyticsAlertsView.d.ts +6 -0
  110. package/dist/features/seller/components/SellerAnalyticsAlertsView.js +124 -0
  111. package/dist/features/seller/components/SellerAnalyticsView.d.ts +2 -0
  112. package/dist/features/seller/components/SellerAnalyticsView.js +17 -9
  113. package/dist/features/seller/components/SellerBidsView.js +33 -3
  114. package/dist/features/seller/components/SellerBundlesView.d.ts +5 -0
  115. package/dist/features/seller/components/SellerBundlesView.js +104 -0
  116. package/dist/features/seller/components/SellerClassifiedView.d.ts +5 -0
  117. package/dist/features/seller/components/SellerClassifiedView.js +113 -0
  118. package/dist/features/seller/components/SellerDashboardView.js +19 -7
  119. package/dist/features/seller/components/SellerDigitalCodesView.d.ts +5 -0
  120. package/dist/features/seller/components/SellerDigitalCodesView.js +115 -0
  121. package/dist/features/seller/components/SellerGoogleReviewsView.d.ts +14 -0
  122. package/dist/features/seller/components/SellerGoogleReviewsView.js +95 -0
  123. package/dist/features/seller/components/SellerGroupedListingsView.d.ts +6 -0
  124. package/dist/features/seller/components/SellerGroupedListingsView.js +112 -0
  125. package/dist/features/seller/components/SellerLiveView.d.ts +5 -0
  126. package/dist/features/seller/components/SellerLiveView.js +113 -0
  127. package/dist/features/seller/components/SellerOrdersView.js +53 -2
  128. package/dist/features/seller/components/SellerPayoutMethodsView.d.ts +7 -0
  129. package/dist/features/seller/components/SellerPayoutMethodsView.js +120 -0
  130. package/dist/features/seller/components/SellerPayoutSettingsView.js +50 -12
  131. package/dist/features/seller/components/SellerProductShell.d.ts +3 -1
  132. package/dist/features/seller/components/SellerProductShell.js +35 -11
  133. package/dist/features/seller/components/SellerProductsCards.d.ts +19 -0
  134. package/dist/features/seller/components/SellerProductsCards.js +19 -0
  135. package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +14 -0
  136. package/dist/features/seller/components/SellerProductsFilterDrawer.js +22 -0
  137. package/dist/features/seller/components/SellerProductsView.d.ts +3 -1
  138. package/dist/features/seller/components/SellerProductsView.js +92 -49
  139. package/dist/features/seller/components/SellerReviewsView.js +67 -4
  140. package/dist/features/seller/components/SellerShippingConfigsView.d.ts +7 -0
  141. package/dist/features/seller/components/SellerShippingConfigsView.js +149 -0
  142. package/dist/features/seller/components/SellerShippingView.js +37 -10
  143. package/dist/features/seller/components/SellerSidebar.js +2 -1
  144. package/dist/features/seller/components/SellerStoreCategoriesView.d.ts +9 -0
  145. package/dist/features/seller/components/SellerStoreCategoriesView.js +122 -0
  146. package/dist/features/seller/components/SellerStorefrontView.d.ts +0 -7
  147. package/dist/features/seller/components/SellerStorefrontView.js +26 -29
  148. package/dist/features/seller/components/SellerTemplatesView.d.ts +10 -0
  149. package/dist/features/seller/components/SellerTemplatesView.js +265 -0
  150. package/dist/features/seller/components/index.d.ts +26 -0
  151. package/dist/features/seller/components/index.js +13 -0
  152. package/dist/features/seller/components/seller-products-styles.d.ts +7 -0
  153. package/dist/features/seller/components/seller-products-styles.js +14 -0
  154. package/dist/features/shell/FormShell.d.ts +7 -1
  155. package/dist/features/shell/FormShell.js +5 -2
  156. package/dist/features/store-extensions/index.d.ts +4 -0
  157. package/dist/features/store-extensions/index.js +4 -0
  158. package/dist/features/store-extensions/repository/rbac.repositories.d.ts +23 -0
  159. package/dist/features/store-extensions/repository/rbac.repositories.js +32 -0
  160. package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +91 -0
  161. package/dist/features/store-extensions/repository/store-extensions.repositories.js +127 -0
  162. package/dist/features/store-extensions/schemas/firestore.d.ts +244 -0
  163. package/dist/features/store-extensions/schemas/firestore.js +158 -0
  164. package/dist/features/store-extensions/schemas/rbac.d.ts +65 -0
  165. package/dist/features/store-extensions/schemas/rbac.js +43 -0
  166. package/dist/features/stores/api/[storeSlug]/reviews/route.js +49 -7
  167. package/dist/features/stores/components/InteractiveStoreCard.js +7 -3
  168. package/dist/features/stores/components/StoreCapabilitiesGuideView.d.ts +3 -0
  169. package/dist/features/stores/components/StoreCapabilitiesGuideView.js +101 -0
  170. package/dist/features/stores/components/StoreFinanceGuideView.d.ts +3 -0
  171. package/dist/features/stores/components/StoreFinanceGuideView.js +79 -0
  172. package/dist/features/stores/components/StoreGuideHubView.d.ts +5 -0
  173. package/dist/features/stores/components/StoreGuideHubView.js +89 -0
  174. package/dist/features/stores/components/StoreListingsGuideView.d.ts +3 -0
  175. package/dist/features/stores/components/StoreListingsGuideView.js +151 -0
  176. package/dist/features/stores/components/StoreOrdersGuideView.d.ts +3 -0
  177. package/dist/features/stores/components/StoreOrdersGuideView.js +122 -0
  178. package/dist/features/stores/components/StoreProductsListing.js +6 -10
  179. package/dist/features/stores/components/StoreReviewsListing.js +100 -23
  180. package/dist/features/stores/components/StoreSettingsGuideView.d.ts +3 -0
  181. package/dist/features/stores/components/StoreSettingsGuideView.js +56 -0
  182. package/dist/features/stores/components/index.d.ts +12 -1
  183. package/dist/features/stores/components/index.js +6 -1
  184. package/dist/features/stores/hooks/useStores.d.ts +5 -0
  185. package/dist/features/stores/hooks/useStores.js +10 -0
  186. package/dist/features/stores/manifest.js +1 -1
  187. package/dist/features/stores/schemas/firestore.d.ts +2 -0
  188. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +13 -2
  189. package/dist/index.d.ts +60 -9
  190. package/dist/index.js +66 -15
  191. package/dist/next/routing/route-map.d.ts +134 -0
  192. package/dist/next/routing/route-map.js +59 -0
  193. package/dist/providers/db-firebase/__tests__/filter-aliases.test.d.ts +1 -0
  194. package/dist/providers/db-firebase/__tests__/filter-aliases.test.js +93 -0
  195. package/dist/providers/db-firebase/sieve.d.ts +49 -0
  196. package/dist/providers/db-firebase/sieve.js +61 -8
  197. package/dist/react/hooks/useFormStatePreservation.d.ts +17 -0
  198. package/dist/react/hooks/useFormStatePreservation.js +62 -0
  199. package/dist/react/hooks/useInlineRowEdit.d.ts +24 -0
  200. package/dist/react/hooks/useInlineRowEdit.js +68 -0
  201. package/dist/react/index.d.ts +4 -0
  202. package/dist/react/index.js +4 -0
  203. package/dist/repositories/index.d.ts +5 -0
  204. package/dist/repositories/index.js +7 -0
  205. package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
  206. package/dist/seed/claimed-coupons-seed-data.d.ts +13 -0
  207. package/dist/seed/claimed-coupons-seed-data.js +79 -0
  208. package/dist/seed/index.d.ts +2 -0
  209. package/dist/seed/index.js +3 -0
  210. package/dist/seed/manifest.js +17 -0
  211. package/dist/seed/store-extensions-seed-data.d.ts +19 -0
  212. package/dist/seed/store-extensions-seed-data.js +421 -0
  213. package/dist/tailwind-utilities.css +1 -1
  214. package/dist/ui/components/Button.style.css +20 -12
  215. package/dist/ui/components/ClaimCouponButton.d.ts +28 -0
  216. package/dist/ui/components/ClaimCouponButton.js +89 -0
  217. package/dist/ui/components/ListingToolbar.d.ts +8 -1
  218. package/dist/ui/components/ListingToolbar.js +4 -2
  219. package/dist/ui/components/QuickCreateModal.d.ts +15 -0
  220. package/dist/ui/components/QuickCreateModal.js +48 -0
  221. package/dist/ui/components/QuickCreateModal.style.css +84 -0
  222. package/dist/ui/components/StickyBottomBar.d.ts +22 -0
  223. package/dist/ui/components/StickyBottomBar.js +20 -0
  224. package/dist/ui/components/VacationBanner.d.ts +11 -0
  225. package/dist/ui/components/VacationBanner.js +16 -0
  226. package/dist/ui/components/__tests__/BulkActionBar.test.d.ts +1 -0
  227. package/dist/ui/components/__tests__/BulkActionBar.test.js +96 -0
  228. package/dist/ui/components/__tests__/ListingToolbar.test.d.ts +1 -0
  229. package/dist/ui/components/__tests__/ListingToolbar.test.js +125 -0
  230. package/dist/ui/index.d.ts +9 -1
  231. package/dist/ui/index.js +4 -0
  232. package/package.json +8 -2
@@ -2,11 +2,11 @@
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 { X, 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";
9
- import { Alert, Badge, BulkActionBar, Button, Div, FilterChipGroup, ListingToolbar, ListingViewShell, Pagination, Row, Span, Text } from "../../../ui";
9
+ import { Alert, Badge, BulkActionBar, Button, Div, ListingToolbar, ListingViewShell, Pagination, Row, Span, Text } from "../../../ui";
10
10
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
11
11
  import { SELLER_PRODUCT_STATUS_TABS } from "../../admin/constants/filter-tabs";
12
12
  import { ROUTES } from "../../../constants";
@@ -14,8 +14,20 @@ import { normalizeListingType } from "../../products/utils/listing-type";
14
14
  import { toRecordArray, toRelativeDate, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
15
15
  import { DataTable } from "../../admin/components/DataTable";
16
16
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
17
+ import { SellerProductsCards } from "./SellerProductsCards";
18
+ import { SellerProductsFilterDrawer } from "./SellerProductsFilterDrawer";
19
+ import { KIND_BADGE_VARIANT } from "./seller-products-styles";
17
20
  const PAGE_SIZE = 25;
18
- const FILTER_KEYS = ["status"];
21
+ const FILTER_KEYS = [
22
+ "status",
23
+ "category",
24
+ "brand",
25
+ "condition",
26
+ "minPrice",
27
+ "maxPrice",
28
+ "tags",
29
+ "badges",
30
+ ];
19
31
  const DEFAULT_SORT = "-createdAt";
20
32
  const SORT_OPTIONS = [
21
33
  { value: "-createdAt", label: "Newest" },
@@ -26,27 +38,22 @@ const SORT_OPTIONS = [
26
38
  { value: "price", label: "Price Low" },
27
39
  ];
28
40
  const STATUS_OPTIONS = SELLER_PRODUCT_STATUS_TABS;
29
- function TypeChips({ active, onChange, }) {
30
- const chips = [
31
- { kind: "all", label: "All" },
32
- { kind: "standard", label: "Standard" },
33
- { kind: "auction", label: "Auction" },
34
- { kind: "pre-order", label: "Pre-order" },
35
- { kind: "prize-draw", label: "Prize Draw" },
41
+ // S-STORE-2-A `<TypeDropdown>` replaces the legacy chip strip. Same width on
42
+ // mobile, narrower on desktop. Drives the `?listingType=` query param.
43
+ function TypeDropdown({ active, onChange, }) {
44
+ const options = [
45
+ { value: "all", label: "All listings" },
46
+ { value: "standard", label: "Standard" },
47
+ { value: "auction", label: "Auction" },
48
+ { value: "pre-order", label: "Pre-order" },
49
+ { value: "prize-draw", label: "Prize Draw" },
50
+ { value: "bundle", label: "Bundle" },
51
+ { value: "classified", label: "Classified" },
52
+ { value: "digital-code", label: "Digital Code" },
53
+ { value: "live", label: "Live" },
36
54
  ];
37
- 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: [
38
- "flex-shrink-0 rounded-full px-3 py-1 text-xs font-medium border transition-colors",
39
- active === kind
40
- ? "bg-[var(--appkit-color-primary)] text-white border-[var(--appkit-color-primary)]"
41
- : "border-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)] hover:border-[var(--appkit-color-primary)] hover:text-[var(--appkit-color-primary)]",
42
- ].join(" "), children: label }, kind))) }));
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))) })] }));
43
56
  }
44
- const KIND_BADGE_VARIANT = {
45
- auction: "warning",
46
- "pre-order": "secondary",
47
- "prize-draw": "primary",
48
- standard: "default",
49
- };
50
57
  const PRODUCT_COLUMNS = [
51
58
  {
52
59
  key: "thumbnail",
@@ -93,15 +100,19 @@ const PRODUCT_COLUMNS = [
93
100
  render: (row) => row.physicalLocation ? (_jsxs(Span, { className: "text-xs font-mono text-[var(--appkit-color-text-muted)]", children: [row.physicalLocation.zone, "/", row.physicalLocation.shelf, "/", row.physicalLocation.bin] })) : (_jsx(Span, { className: "text-xs text-[var(--appkit-color-text-faint)]", children: "\u2014" })),
94
101
  },
95
102
  ];
96
- export function SellerProductsView({ onDeleteProduct, children, ...props }) {
103
+ export function SellerProductsView({ onDeleteProduct, onCreateClick, children, ...props }) {
97
104
  const hasChildren = React.Children.count(children) > 0;
98
105
  const [view, setView] = useState("table");
99
106
  const dispatch = useActionDispatch();
100
107
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
101
108
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
102
109
  const [filterOpen, setFilterOpen] = useState(false);
103
- const [listingKind, setListingKind] = useState("all");
110
+ // listingKind is URL-driven so it survives navigation and back/forward
111
+ const listingKind = (table.get("listingType") || "all");
104
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());
105
116
  const [setLocationOpen, setSetLocationOpen] = useState(false);
106
117
  const [pendingFilters, setPendingFilters] = useState(() => Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
107
118
  const openFilters = useCallback(() => {
@@ -119,39 +130,33 @@ export function SellerProductsView({ onDeleteProduct, children, ...props }) {
119
130
  setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, ""])));
120
131
  }, []);
121
132
  const resetAll = useCallback(() => {
122
- const updates = { q: "", sort: "" };
133
+ const updates = { q: "", sort: "", listingType: "", showSold: "" };
123
134
  for (const k of FILTER_KEYS)
124
135
  updates[k] = "";
125
136
  table.setMany(updates);
126
137
  setSearchInput("");
127
- setListingKind("all");
128
138
  }, [table]);
129
139
  const commitSearch = useCallback(() => {
130
140
  table.set("q", searchInput.trim());
131
141
  }, [searchInput, table]);
132
142
  const handleKindChange = useCallback((kind) => {
133
- setListingKind(kind);
134
- 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" });
135
145
  }, [table]);
146
+ const showSold = table.get("showSold") === "true";
136
147
  const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
137
148
  const hasActiveState = !!table.get("q") ||
138
149
  table.get("sort") !== DEFAULT_SORT ||
139
150
  activeFilterCount > 0 ||
140
- listingKind !== "all";
151
+ listingKind !== "all" ||
152
+ showSold;
141
153
  const statusRaw = table.get("status");
142
154
  const statusFilter = statusRaw && statusRaw !== "All" ? `status==${statusRaw}` : undefined;
143
155
  // SB1-G — single-field listingType clause. The repository's Sieve aliases
144
156
  // accept both `==auction|preorder|standard` and `==pre-order` directly.
145
- const kindFilter = listingKind === "auction"
146
- ? "listingType==auction"
147
- : listingKind === "pre-order"
148
- ? "listingType==pre-order"
149
- : listingKind === "prize-draw"
150
- ? "listingType==prize-draw"
151
- : listingKind === "standard"
152
- ? "listingType==standard"
153
- : undefined;
154
- 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;
155
160
  const { rows, total, isLoading, errorMessage } = useSellerListingData({
156
161
  queryKey: ["seller", "products", "listing", listingKind],
157
162
  endpoint: SELLER_ENDPOINTS.PRODUCTS,
@@ -190,7 +195,10 @@ export function SellerProductsView({ onDeleteProduct, children, ...props }) {
190
195
  });
191
196
  const currentPage = table.getNumber("page", 1);
192
197
  const totalPages = Math.ceil(total / PAGE_SIZE);
193
- 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 });
194
202
  if (hasChildren) {
195
203
  return (_jsx(ListingViewShell, { portal: "seller", ...props, children: children }));
196
204
  }
@@ -210,11 +218,43 @@ export function SellerProductsView({ onDeleteProduct, children, ...props }) {
210
218
  setDeletingId(row.id);
211
219
  try {
212
220
  await onDeleteProduct(row.id);
221
+ // Optimistically remove from list — avoids a full refetch
222
+ setDeletedIds((prev) => new Set([...prev, row.id]));
213
223
  }
214
224
  finally {
215
225
  setDeletingId(null);
216
226
  }
217
227
  };
228
+ // S-STORE-2-C — Duplicate verb. Server-side endpoint is /api/store/products/[id]/duplicate.
229
+ const handleDuplicate = async (row) => {
230
+ const res = await fetch(`/api/store/products/${row.id}/duplicate`, {
231
+ method: "POST",
232
+ }).catch(() => null);
233
+ if (res && res.ok) {
234
+ const json = await res.json().catch(() => null);
235
+ const newId = json?.data?.id;
236
+ if (newId)
237
+ handleEdit({ ...row, id: newId });
238
+ }
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
+ };
218
258
  const handleBulkPrintLabels = useCallback(() => {
219
259
  const ids = selection.selectedIds.join(",");
220
260
  void dispatch({
@@ -244,15 +284,18 @@ export function SellerProductsView({ onDeleteProduct, children, ...props }) {
244
284
  onClick: () => setSetLocationOpen(true),
245
285
  },
246
286
  ];
247
- 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 }), _jsx(TypeChips, { 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 })), _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"
248
290
  ? `No ${listingKind} listings found`
249
- : "No products listed yet", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => selection.toggleAll(), getRowHref: (row) => row.listingKind === "auction"
250
- ? String(ROUTES.STORE.AUCTIONS_EDIT(row.id))
291
+ : "No products listed yet", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: () => selection.toggleAll(), getRowHref: (row) =>
292
+ // S-STORE-2-D — row click → public detail/preview, not edit.
293
+ row.listingKind === "auction"
294
+ ? `/auctions/${row.id}`
251
295
  : row.listingKind === "pre-order"
252
- ? String(ROUTES.STORE.PRE_ORDERS_EDIT(row.id))
253
- : row.listingKind === "prize-draw"
254
- ? String(ROUTES.STORE.PRIZE_DRAWS_EDIT(row.id))
255
- : String(ROUTES.STORE.PRODUCTS_EDIT(row.id)), renderRowActions: onDeleteProduct
256
- ? (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", 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" }) })] }))
257
- : undefined })] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx(Div, { role: "presentation", className: "fixed inset-0 z-40 bg-black/40", onClick: () => setFilterOpen(false) }), _jsxs(Div, { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-[var(--appkit-color-surface)] shadow-2xl", children: [_jsxs(Row, { justify: "between", className: "border-b border-[var(--appkit-color-border)] px-4 py-3.5", children: [_jsx(Text, { className: "text-base font-semibold text-[var(--appkit-color-text)]", children: "Filters" }), _jsxs(Row, { className: "gap-2", children: [activeFilterCount > 0 && (_jsx(Button, { variant: "ghost", size: "sm", onClick: clearFilters, className: "text-xs text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-error)]", children: "Clear all" })), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setFilterOpen(false), "aria-label": "Close filters", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx(Div, { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _jsx(Div, { className: "border-t border-[var(--appkit-color-border)] px-4 py-3.5", children: _jsxs(Button, { variant: "primary", onClick: applyFilters, className: "w-full rounded-lg py-2.5 active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }), setLocationOpen && (_jsx(PhysicalLocationModal, { count: selection.selectedIds.length, onSave: handleSetLocation, onClose: () => setSetLocationOpen(false) }))] }));
296
+ ? `/pre-orders/${row.id}`
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) }))] }));
258
301
  }
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useCallback, useEffect, useState } from "react";
4
- import { Alert, Badge, Button, Div, Select, SideDrawer, Stack, Text, } from "../../../ui";
3
+ import { useCallback, useEffect, useMemo, useState } from "react";
4
+ import { Alert, Badge, BulkActionBar, Button, Checkbox, Div, Modal, Row, Select, SideDrawer, Stack, Text, Textarea, } from "../../../ui";
5
5
  import { StackedViewShell } from "../../../ui";
6
6
  const STAR = "★";
7
7
  const EMPTY_STAR = "☆";
@@ -30,6 +30,62 @@ export function SellerReviewsView({ reviewsApiBase = "/api/store/reviews", reply
30
30
  const [replyText, setReplyText] = useState("");
31
31
  const [replySaving, setReplySaving] = useState(false);
32
32
  const [replyError, setReplyError] = useState(null);
33
+ // S-STORE-4-C — bulk reply + contest + feedback selection
34
+ const [selectedIds, setSelectedIds] = useState(new Set());
35
+ const [bulkReplyOpen, setBulkReplyOpen] = useState(false);
36
+ const [bulkReplyText, setBulkReplyText] = useState("");
37
+ const [bulkSaving, setBulkSaving] = useState(false);
38
+ const [contestTarget, setContestTarget] = useState(null);
39
+ const [contestReason, setContestReason] = useState("");
40
+ const [feedbackTarget, setFeedbackTarget] = useState(null);
41
+ const [feedbackText, setFeedbackText] = useState("");
42
+ const toggleSelected = useCallback((id) => {
43
+ setSelectedIds((prev) => {
44
+ const next = new Set(prev);
45
+ next.has(id) ? next.delete(id) : next.add(id);
46
+ return next;
47
+ });
48
+ }, []);
49
+ const clearSelection = useCallback(() => setSelectedIds(new Set()), []);
50
+ const submitBulkReply = useCallback(async () => {
51
+ if (!bulkReplyText.trim() || selectedIds.size === 0)
52
+ return;
53
+ setBulkSaving(true);
54
+ await Promise.all(Array.from(selectedIds).map((id) => fetch(`${replyApiBase}/${id}/reply`, {
55
+ method: "POST",
56
+ headers: { "Content-Type": "application/json" },
57
+ body: JSON.stringify({ reply: bulkReplyText }),
58
+ }).catch(() => null)));
59
+ setBulkSaving(false);
60
+ setBulkReplyOpen(false);
61
+ setBulkReplyText("");
62
+ clearSelection();
63
+ fetchReviewsRef.current?.();
64
+ }, [bulkReplyText, selectedIds, replyApiBase, clearSelection]);
65
+ const submitContest = useCallback(async () => {
66
+ if (!contestTarget)
67
+ return;
68
+ await fetch(`/api/store/reviews/${contestTarget.id}/contest`, {
69
+ method: "POST",
70
+ headers: { "Content-Type": "application/json" },
71
+ body: JSON.stringify({ reason: contestReason }),
72
+ }).catch(() => null);
73
+ setContestTarget(null);
74
+ setContestReason("");
75
+ fetchReviewsRef.current?.();
76
+ }, [contestTarget, contestReason]);
77
+ const submitFeedback = useCallback(async () => {
78
+ if (!feedbackTarget)
79
+ return;
80
+ await fetch(`/api/store/reviews/${feedbackTarget.id}/feedback`, {
81
+ method: "POST",
82
+ headers: { "Content-Type": "application/json" },
83
+ body: JSON.stringify({ feedback: feedbackText }),
84
+ }).catch(() => null);
85
+ setFeedbackTarget(null);
86
+ setFeedbackText("");
87
+ }, [feedbackTarget, feedbackText]);
88
+ const fetchReviewsRef = useMemo(() => ({ current: null }), []);
33
89
  const fetchReviews = useCallback(async () => {
34
90
  setLoading(true);
35
91
  setError(null);
@@ -54,6 +110,7 @@ export function SellerReviewsView({ reviewsApiBase = "/api/store/reviews", reply
54
110
  }
55
111
  }, [reviewsApiBase, rating, replied, page]);
56
112
  useEffect(() => { fetchReviews(); }, [fetchReviews]);
113
+ useEffect(() => { fetchReviewsRef.current = fetchReviews; }, [fetchReviews, fetchReviewsRef]);
57
114
  const openReply = (review) => {
58
115
  setReplyTarget(review);
59
116
  setReplyText(review.sellerReply ?? "");
@@ -95,6 +152,12 @@ export function SellerReviewsView({ reviewsApiBase = "/api/store/reviews", reply
95
152
  { value: "", label: "All reply statuses" },
96
153
  { value: "true", label: "Store replied" },
97
154
  { value: "false", label: "Awaiting reply" },
98
- ] }), meta && (_jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] ml-auto", children: [meta.total, " review", meta.total !== 1 ? "s" : ""] }))] }), error && _jsx(Alert, { variant: "error", children: error }), loading ? (_jsx(Div, { className: "py-8 text-center", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Loading reviews\u2026" }) })) : reviews.length === 0 ? (_jsx(Div, { className: "py-12 text-center", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "No reviews found." }) })) : (_jsx(Stack, { gap: "md", children: reviews.map((review) => (_jsx(Div, { className: "p-4 rounded-lg border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", children: _jsxs(Div, { className: "flex items-start justify-between gap-3 flex-wrap", children: [_jsxs(Div, { className: "flex-1 min-w-0", children: [_jsx(Text, { className: "font-medium truncate", children: review.productTitle }), _jsxs(Div, { className: "flex items-center gap-2 mt-1 flex-wrap", children: [_jsx(Stars, { rating: review.rating }), _jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: ["by ", review.userName] }), review.verified && _jsx(Badge, { variant: "success", children: "Verified" }), statusBadge(review.status), _jsx(Badge, { variant: review.sellerReply ? "success" : "warning", children: review.sellerReply ? "Store replied" : "Awaiting store reply" })] }), review.title && _jsx(Text, { className: "mt-2 font-medium", children: review.title }), _jsx(Text, { className: "mt-1 text-sm text-[var(--appkit-color-text-secondary)] line-clamp-3", children: review.comment }), review.sellerReply && (_jsxs(Div, { className: "mt-2 pl-3 border-l-2 border-[var(--appkit-color-primary)]", children: [_jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)]", children: "Store reply:" }), _jsx(Text, { className: "text-sm", children: review.sellerReply })] }))] }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => openReply(review), children: review.sellerReply ? "Edit Reply" : "Reply" })] }) }, review.id))) })), meta && meta.totalPages > 1 && (_jsxs(Div, { className: "flex items-center justify-center gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => setPage((p) => Math.max(1, p - 1)), disabled: page <= 1 || loading, children: "Previous" }), _jsxs(Text, { className: "text-sm", children: ["Page ", meta.page, " of ", meta.totalPages] }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => setPage((p) => p + 1), disabled: !meta.hasMore || loading, children: "Next" })] }))] }, "reviews"),
99
- ] }), _jsx(SideDrawer, { isOpen: !!replyTarget, onClose: () => setReplyTarget(null), title: replyTarget?.sellerReply ? "Edit Reply" : "Reply to Review", mode: "create", children: _jsxs(Stack, { gap: "md", className: "p-4", children: [replyTarget && (_jsxs(Div, { className: "p-3 rounded bg-[var(--appkit-color-surface-muted)]", children: [_jsx(Stars, { rating: replyTarget.rating }), _jsx(Text, { className: "text-sm mt-1", children: replyTarget.comment })] })), replyError && _jsx(Alert, { variant: "error", children: replyError }), _jsxs(Div, { children: [_jsx(Text, { className: "text-sm font-medium mb-1.5", children: "Store reply" }), _jsx("textarea", { className: "w-full rounded-md border border-[var(--appkit-color-border)] bg-[var(--appkit-color-input-bg,var(--appkit-color-surface))] p-2.5 text-sm resize-none focus:outline-none focus:ring-2 focus:ring-[var(--appkit-color-primary)] dark:bg-[var(--appkit-color-surface-dark,#1e2330)] dark:border-[var(--appkit-color-border-dark)]", rows: 5, value: replyText, onChange: (e) => setReplyText(e.target.value), maxLength: 1000, placeholder: "Write your response to this review\u2026" }), _jsxs(Text, { className: "text-xs text-[var(--appkit-color-text-muted)] text-right mt-1", children: [replyText.length, "/1000"] })] }), _jsxs(Div, { className: "flex gap-2 justify-end", children: [_jsx(Button, { variant: "outline", onClick: () => setReplyTarget(null), disabled: replySaving, children: "Cancel" }), _jsx(Button, { variant: "primary", onClick: handleReplySave, disabled: replySaving || !replyText.trim(), isLoading: replySaving, children: replyTarget?.sellerReply ? "Update Reply" : "Post Reply" })] })] }) })] }));
155
+ ] }), meta && (_jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] ml-auto", children: [meta.total, " review", meta.total !== 1 ? "s" : ""] }))] }), error && _jsx(Alert, { variant: "error", children: error }), selectedIds.size > 0 && (_jsx(BulkActionBar, { selectedCount: selectedIds.size, onClearSelection: clearSelection, actions: [
156
+ {
157
+ id: "bulk-reply",
158
+ label: "Reply to selected",
159
+ onClick: () => setBulkReplyOpen(true),
160
+ },
161
+ ] })), loading ? (_jsx(Div, { className: "py-8 text-center", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Loading reviews\u2026" }) })) : reviews.length === 0 ? (_jsx(Div, { className: "py-12 text-center", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "No reviews found." }) })) : (_jsx(Stack, { gap: "md", children: reviews.map((review) => (_jsx(Div, { className: "p-4 rounded-lg border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", children: _jsxs(Div, { className: "flex items-start justify-between gap-3 flex-wrap", children: [_jsxs(Row, { className: "gap-3 items-start flex-1 min-w-0", children: [_jsx(Checkbox, { checked: selectedIds.has(review.id), onChange: () => toggleSelected(review.id), "aria-label": "Select review" }), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsx(Text, { className: "font-medium truncate", children: review.productTitle }), _jsxs(Div, { className: "flex items-center gap-2 mt-1 flex-wrap", children: [_jsx(Stars, { rating: review.rating }), _jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: ["by ", review.userName] }), review.verified && _jsx(Badge, { variant: "success", children: "Verified" }), statusBadge(review.status), _jsx(Badge, { variant: review.sellerReply ? "success" : "warning", children: review.sellerReply ? "Store replied" : "Awaiting store reply" })] }), review.title && _jsx(Text, { className: "mt-2 font-medium", children: review.title }), _jsx(Text, { className: "mt-1 text-sm text-[var(--appkit-color-text-secondary)] line-clamp-3", children: review.comment }), review.sellerReply && (_jsxs(Div, { className: "mt-2 pl-3 border-l-2 border-[var(--appkit-color-primary)]", children: [_jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)]", children: "Store reply:" }), _jsx(Text, { className: "text-sm", children: review.sellerReply })] }))] })] }), _jsxs(Row, { className: "gap-2 flex-shrink-0", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => openReply(review), children: review.sellerReply ? "Edit Reply" : "Reply" }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setContestTarget(review), children: "Contest" }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setFeedbackTarget(review), children: "Feedback" })] })] }) }, review.id))) })), meta && meta.totalPages > 1 && (_jsxs(Div, { className: "flex items-center justify-center gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => setPage((p) => Math.max(1, p - 1)), disabled: page <= 1 || loading, children: "Previous" }), _jsxs(Text, { className: "text-sm", children: ["Page ", meta.page, " of ", meta.totalPages] }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => setPage((p) => p + 1), disabled: !meta.hasMore || loading, children: "Next" })] }))] }, "reviews"),
162
+ ] }), _jsx(SideDrawer, { isOpen: !!replyTarget, onClose: () => setReplyTarget(null), title: replyTarget?.sellerReply ? "Edit Reply" : "Reply to Review", mode: "create", children: _jsxs(Stack, { gap: "md", className: "p-4", children: [replyTarget && (_jsxs(Div, { className: "p-3 rounded bg-[var(--appkit-color-surface-muted)]", children: [_jsx(Stars, { rating: replyTarget.rating }), _jsx(Text, { className: "text-sm mt-1", children: replyTarget.comment })] })), replyError && _jsx(Alert, { variant: "error", children: replyError }), _jsxs(Div, { children: [_jsx(Text, { className: "text-sm font-medium mb-1.5", children: "Store reply" }), _jsx("textarea", { className: "w-full rounded-md border border-[var(--appkit-color-border)] bg-[var(--appkit-color-input-bg,var(--appkit-color-surface))] p-2.5 text-sm resize-none focus:outline-none focus:ring-2 focus:ring-[var(--appkit-color-primary)] dark:bg-[var(--appkit-color-surface-dark,#1e2330)] dark:border-[var(--appkit-color-border-dark)]", rows: 5, value: replyText, onChange: (e) => setReplyText(e.target.value), maxLength: 1000, placeholder: "Write your response to this review\u2026" }), _jsxs(Text, { className: "text-xs text-[var(--appkit-color-text-muted)] text-right mt-1", children: [replyText.length, "/1000"] })] }), _jsxs(Div, { className: "flex gap-2 justify-end", children: [_jsx(Button, { variant: "outline", onClick: () => setReplyTarget(null), disabled: replySaving, children: "Cancel" }), _jsx(Button, { variant: "primary", onClick: handleReplySave, disabled: replySaving || !replyText.trim(), isLoading: replySaving, children: replyTarget?.sellerReply ? "Update Reply" : "Post Reply" })] })] }) }), _jsx(Modal, { isOpen: bulkReplyOpen, onClose: () => setBulkReplyOpen(false), title: `Bulk reply to ${selectedIds.size} review${selectedIds.size === 1 ? "" : "s"}`, actions: _jsxs(Row, { className: "gap-2", children: [_jsx(Button, { variant: "ghost", onClick: () => setBulkReplyOpen(false), disabled: bulkSaving, children: "Cancel" }), _jsx(Button, { variant: "primary", onClick: () => void submitBulkReply(), disabled: !bulkReplyText.trim() || bulkSaving, isLoading: bulkSaving, children: "Send reply" })] }), children: _jsxs(Stack, { gap: "md", className: "p-1", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "The same reply will be posted on all selected reviews." }), _jsx(Textarea, { value: bulkReplyText, onChange: (e) => setBulkReplyText(e.target.value), rows: 5, placeholder: "Thanks for your review\u2026", label: "Reply" })] }) }), _jsx(Modal, { isOpen: !!contestTarget, onClose: () => setContestTarget(null), title: "Contest this review", actions: _jsxs(Row, { className: "gap-2", children: [_jsx(Button, { variant: "ghost", onClick: () => setContestTarget(null), children: "Cancel" }), _jsx(Button, { variant: "primary", onClick: () => void submitContest(), disabled: !contestReason.trim(), children: "Submit" })] }), children: _jsxs(Stack, { gap: "md", className: "p-1", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Flag this review for admin investigation. Provide a clear reason \u2014 fake, abusive, off-topic, etc." }), _jsx(Textarea, { value: contestReason, onChange: (e) => setContestReason(e.target.value), rows: 4, label: "Reason" })] }) }), _jsx(Modal, { isOpen: !!feedbackTarget, onClose: () => setFeedbackTarget(null), title: "Send feedback to buyer", actions: _jsxs(Row, { className: "gap-2", children: [_jsx(Button, { variant: "ghost", onClick: () => setFeedbackTarget(null), children: "Cancel" }), _jsx(Button, { variant: "primary", onClick: () => void submitFeedback(), disabled: !feedbackText.trim(), children: "Send" })] }), children: _jsxs(Stack, { gap: "md", className: "p-1", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Private message sent to the buyer's notification inbox. Does not appear on the public review." }), _jsx(Textarea, { value: feedbackText, onChange: (e) => setFeedbackText(e.target.value), rows: 4, label: "Feedback" })] }) })] }));
100
163
  }
@@ -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
+ }