@mohasinac/appkit 4.1.1 → 4.2.0

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 (225) hide show
  1. package/dist/_internal/client/features/maintenance/index.d.ts +2 -0
  2. package/dist/_internal/client/features/maintenance/index.js +1 -0
  3. package/dist/_internal/client/features/maintenance/views/CloudLogsListView.d.ts +9 -0
  4. package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +55 -0
  5. package/dist/_internal/server/features/auctions/index.d.ts +1 -1
  6. package/dist/_internal/server/features/auctions/index.js +1 -1
  7. package/dist/_internal/server/features/auctions/service.js +2 -2
  8. package/dist/_internal/server/features/checkout/actions.d.ts +15 -0
  9. package/dist/_internal/server/features/checkout/actions.js +245 -182
  10. package/dist/_internal/server/features/maintenance/cloud-logs-data.d.ts +50 -0
  11. package/dist/_internal/server/features/maintenance/cloud-logs-data.js +152 -0
  12. package/dist/_internal/server/functions/scheduled.d.ts +2 -1
  13. package/dist/_internal/server/functions/scheduled.js +9 -1
  14. package/dist/_internal/server/jobs/core/jobRunners.js +10 -0
  15. package/dist/_internal/server/jobs/core/newsletterExport.d.ts +15 -0
  16. package/dist/_internal/server/jobs/core/newsletterExport.js +43 -0
  17. package/dist/_internal/server/jobs/core/onOrderCreate.d.ts +4 -12
  18. package/dist/_internal/server/jobs/core/onOrderCreate.js +3 -3
  19. package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +2 -0
  20. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +1 -0
  21. package/dist/_internal/server/jobs/core/onProductWrite.js +14 -2
  22. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +1 -0
  23. package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +1 -0
  24. package/dist/_internal/server/jobs/core/revenueRollup.d.ts +13 -0
  25. package/dist/_internal/server/jobs/core/revenueRollup.js +22 -0
  26. package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +35 -0
  27. package/dist/_internal/server/jobs/core/whatsappNotify.js +82 -0
  28. package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
  29. package/dist/_internal/server/jobs/handlers/index.js +1 -0
  30. package/dist/_internal/server/jobs/handlers/revenueRollup.d.ts +2 -0
  31. package/dist/_internal/server/jobs/handlers/revenueRollup.js +2 -0
  32. package/dist/_internal/shared/features/auctions/config.d.ts +1 -1
  33. package/dist/_internal/shared/features/auctions/config.js +1 -1
  34. package/dist/_internal/shared/features/auctions/schema.js +1 -1
  35. package/dist/_internal/shared/features/cart/schema.js +2 -2
  36. package/dist/_internal/shared/features/checkout/config.d.ts +7 -0
  37. package/dist/_internal/shared/features/checkout/config.js +7 -0
  38. package/dist/_internal/shared/features/events/schema.js +1 -1
  39. package/dist/_internal/shared/features/orders/schema.js +1 -1
  40. package/dist/_internal/shared/features/products/schema.js +3 -3
  41. package/dist/_internal/shared/features/promotions/schema.js +3 -3
  42. package/dist/_internal/shared/fees/calculator.d.ts +32 -7
  43. package/dist/_internal/shared/fees/calculator.js +25 -0
  44. package/dist/client.d.ts +4 -4
  45. package/dist/client.js +4 -2
  46. package/dist/constants/api-endpoints.d.ts +9 -6
  47. package/dist/constants/api-endpoints.js +3 -2
  48. package/dist/contracts/client-payment-gateway.d.ts +1 -2
  49. package/dist/features/about/components/AboutView.d.ts +6 -16
  50. package/dist/features/about/components/AboutView.js +3 -3
  51. package/dist/features/about/components/FeesView.js +18 -0
  52. package/dist/features/about/index.d.ts +2 -1
  53. package/dist/features/about/schemas/firestore.d.ts +47 -0
  54. package/dist/features/about/schemas/firestore.js +6 -0
  55. package/dist/features/about/schemas/index.d.ts +210 -0
  56. package/dist/features/about/schemas/index.js +45 -0
  57. package/dist/features/account/hooks/useProfile.js +3 -0
  58. package/dist/features/admin/actions/notification-actions.d.ts +4 -1
  59. package/dist/features/admin/actions/notification-actions.js +56 -10
  60. package/dist/features/admin/components/AdminAdsView.js +1 -1
  61. package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +6 -0
  62. package/dist/features/admin/components/AdminCarouselGroupEditorView.js +50 -0
  63. package/dist/features/admin/components/AdminCartsView.js +16 -3
  64. package/dist/features/admin/components/AdminCouponEditorView.js +2 -2
  65. package/dist/features/admin/components/AdminCouponsView.js +14 -4
  66. package/dist/features/admin/components/AdminEmployeeEditorView.js +1 -42
  67. package/dist/features/admin/components/AdminMediaView.js +1 -1
  68. package/dist/features/admin/components/AdminNewsletterView.js +42 -11
  69. package/dist/features/admin/components/AdminNotificationsView.js +31 -19
  70. package/dist/features/admin/components/AdminOrdersView.js +7 -0
  71. package/dist/features/admin/components/AdminPayoutsView.js +18 -3
  72. package/dist/features/admin/components/AdminProductsView.js +17 -2
  73. package/dist/features/admin/components/AdminReviewsView.js +16 -2
  74. package/dist/features/admin/components/AdminScammersView.js +7 -0
  75. package/dist/features/admin/components/AdminSectionsView.js +4 -0
  76. package/dist/features/admin/components/AdminSiteConfigGuideView.js +1 -1
  77. package/dist/features/admin/components/AdminSiteSettingsView.js +127 -8
  78. package/dist/features/admin/components/AdminStoreEditorView.d.ts +2 -1
  79. package/dist/features/admin/components/AdminStoreEditorView.js +4 -4
  80. package/dist/features/admin/components/AdminStoresView.js +1 -1
  81. package/dist/features/admin/components/AdminSupportTicketDetailView.js +23 -5
  82. package/dist/features/admin/components/AdminSupportTicketsView.js +20 -2
  83. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +4 -1
  84. package/dist/features/admin/components/AdminTesterChecklistView.js +8 -0
  85. package/dist/features/admin/components/index.d.ts +2 -0
  86. package/dist/features/admin/components/index.js +1 -0
  87. package/dist/features/admin/constants/filter-tabs.d.ts +55 -41
  88. package/dist/features/admin/constants/filter-tabs.js +55 -27
  89. package/dist/features/admin/repository/analytics-rollup.repository.d.ts +25 -0
  90. package/dist/features/admin/repository/analytics-rollup.repository.js +31 -0
  91. package/dist/features/admin/repository/site-settings.repository.js +8 -1
  92. package/dist/features/admin/schemas/firestore.d.ts +46 -0
  93. package/dist/features/admin/schemas/firestore.js +62 -24
  94. package/dist/features/auctions/actions/bid-actions.js +1 -1
  95. package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
  96. package/dist/features/auctions/components/AuctionsListView.js +8 -1
  97. package/dist/features/auth/components/LoginForm.js +2 -2
  98. package/dist/features/auth/components/RegisterForm.js +2 -2
  99. package/dist/features/auth/permissions/constants.d.ts +16 -0
  100. package/dist/features/auth/permissions/constants.js +53 -0
  101. package/dist/features/auth/schemas/firestore.js +1 -0
  102. package/dist/features/blog/components/BlogFilters.js +1 -1
  103. package/dist/features/cart/schemas/firestore.d.ts +1 -1
  104. package/dist/features/categories/schemas/firestore.d.ts +1 -1
  105. package/dist/features/consultation/components/ConsultationForm.js +1 -1
  106. package/dist/features/events/components/EventFilters.js +2 -0
  107. package/dist/features/homepage/components/WelcomeSection.js +2 -2
  108. package/dist/features/layout/BackToTop.d.ts +9 -5
  109. package/dist/features/layout/BackToTop.js +17 -21
  110. package/dist/features/layout/TitleBarLayout.d.ts +2 -2
  111. package/dist/features/layout/TitleBarLayout.js +4 -4
  112. package/dist/features/media/AvatarUpload.js +7 -3
  113. package/dist/features/media/MediaVideo.js +1 -1
  114. package/dist/features/media/finalize.d.ts +9 -13
  115. package/dist/features/media/finalize.js +23 -74
  116. package/dist/features/media/server.d.ts +1 -1
  117. package/dist/features/media/server.js +1 -1
  118. package/dist/features/orders/actions/refund-actions.js +1 -0
  119. package/dist/features/orders/hooks/useOrders.js +16 -4
  120. package/dist/features/orders/schemas/firestore.d.ts +19 -5
  121. package/dist/features/payments/schemas/firestore.d.ts +2 -2
  122. package/dist/features/pre-orders/components/PreOrdersListView.js +7 -0
  123. package/dist/features/products/components/ArtStickersListView.js +5 -0
  124. package/dist/features/products/components/ProductDetailPageView.d.ts +7 -1
  125. package/dist/features/products/components/ProductForm.js +14 -1
  126. package/dist/features/products/components/ProductsIndexPageView.js +7 -0
  127. package/dist/features/products/constants/action-defs.d.ts +29 -3
  128. package/dist/features/products/constants/action-defs.js +77 -1
  129. package/dist/features/products/schemas/firestore.d.ts +1 -1
  130. package/dist/features/products/schemas/product-templates.d.ts +1 -1
  131. package/dist/features/products/types/index.d.ts +4 -0
  132. package/dist/features/scams/schemas/index.js +2 -2
  133. package/dist/features/search/components/Search.js +3 -3
  134. package/dist/features/seller/components/SellerAuctionsView.js +27 -2
  135. package/dist/features/seller/components/SellerOrdersView.js +2 -2
  136. package/dist/features/seller/components/SellerPayoutsView.js +24 -10
  137. package/dist/features/seller/components/SellerPreOrdersView.js +12 -4
  138. package/dist/features/seller/components/SellerPrizeDrawsView.js +23 -5
  139. package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
  140. package/dist/features/seller/components/SellerProductShell.js +1 -1
  141. package/dist/features/seller/components/SellerProductsView.js +8 -6
  142. package/dist/features/seller/components/SellerReviewsView.js +27 -6
  143. package/dist/features/seller/components/index.d.ts +0 -2
  144. package/dist/features/seller/components/index.js +0 -1
  145. package/dist/features/stores/components/StoreListingsGuideView.js +2 -1
  146. package/dist/features/stores/components/StoreProductsView.js +1 -1
  147. package/dist/features/stores/schemas/firestore.d.ts +11 -0
  148. package/dist/features/stores/schemas/firestore.js +5 -0
  149. package/dist/features/stores/schemas/index.d.ts +2 -2
  150. package/dist/features/tester/actions/checklist-item-actions.d.ts +6 -0
  151. package/dist/features/tester/actions/checklist-item-actions.js +2 -0
  152. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +13 -2
  153. package/dist/features/tester/components/AdminTesterFeedbackListView.js +2 -1
  154. package/dist/features/tester/components/AdminTesterFeedbackReportView.js +9 -5
  155. package/dist/features/tester/components/TesterHubView.js +34 -21
  156. package/dist/features/tester/repository/tester-checklist-item.repository.js +1 -0
  157. package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +18 -4
  158. package/dist/features/tester/repository/tester-checklist-response.repository.js +43 -8
  159. package/dist/features/tester/schemas/firestore.d.ts +8 -3
  160. package/dist/features/tester/schemas/firestore.js +6 -0
  161. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +21 -9
  162. package/dist/features/tester/utils/phases.d.ts +31 -0
  163. package/dist/features/tester/utils/phases.js +46 -0
  164. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +1 -1
  165. package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +10 -1
  166. package/dist/features/whatsapp-bot/helpers/whatsapp.js +47 -0
  167. package/dist/features/whatsapp-bot/types/index.d.ts +20 -1
  168. package/dist/features/wishlist/types/index.d.ts +3 -0
  169. package/dist/index.d.ts +15 -6
  170. package/dist/index.js +19 -9
  171. package/dist/next/components/NotFoundView.js +2 -1
  172. package/dist/next/routing/route-map.d.ts +6 -12
  173. package/dist/next/routing/route-map.js +3 -4
  174. package/dist/providers/db-firebase/admin.d.ts +1 -0
  175. package/dist/providers/db-firebase/admin.js +1 -1
  176. package/dist/repositories/index.d.ts +2 -0
  177. package/dist/repositories/index.js +1 -0
  178. package/dist/schemas/registry.d.ts +2 -2
  179. package/dist/seed/addresses-seed-data.js +18 -18
  180. package/dist/seed/bids-seed-data.js +4 -4
  181. package/dist/seed/blog-posts-seed-data.js +82 -82
  182. package/dist/seed/carousel-slides-seed-data.js +14 -10
  183. package/dist/seed/carousels-seed-data.js +3 -3
  184. package/dist/seed/cart-seed-data.js +92 -92
  185. package/dist/seed/claimed-coupons-seed-data.js +10 -10
  186. package/dist/seed/conversations-seed-data.js +75 -75
  187. package/dist/seed/coupon-usage-seed-data.js +12 -12
  188. package/dist/seed/coupons-seed-data.js +41 -41
  189. package/dist/seed/events-seed-data.js +105 -105
  190. package/dist/seed/grouped-listings-seed-data.d.ts +2 -137
  191. package/dist/seed/grouped-listings-seed-data.js +79 -143
  192. package/dist/seed/history-seed-data.js +38 -38
  193. package/dist/seed/homepage-sections-seed-data.js +9 -9
  194. package/dist/seed/notifications-seed-data.js +56 -56
  195. package/dist/seed/offers-seed-data.js +91 -91
  196. package/dist/seed/orders-seed-data.js +121 -106
  197. package/dist/seed/payouts-seed-data.js +42 -42
  198. package/dist/seed/products-auctions-seed-data.js +10 -2
  199. package/dist/seed/products-standard-seed-data.js +40 -8
  200. package/dist/seed/reviews-seed-data.js +42 -23
  201. package/dist/seed/scammers-seed-data.js +22 -22
  202. package/dist/seed/sessions-seed-data.js +7 -7
  203. package/dist/seed/shipments-seed-data.js +10 -10
  204. package/dist/seed/site-settings-seed-data.js +89 -0
  205. package/dist/seed/store-addresses-seed-data.js +2 -2
  206. package/dist/seed/store-extensions-seed-data.js +93 -93
  207. package/dist/seed/stores-seed-data.js +2 -0
  208. package/dist/seed/support-tickets-seed-data.js +47 -47
  209. package/dist/seed/users-seed-data.js +55 -89
  210. package/dist/seed/wishlists-seed-data.js +22 -22
  211. package/dist/server.d.ts +4 -3
  212. package/dist/server.js +7 -7
  213. package/dist/styles.css +1 -1
  214. package/dist/tailwind-utilities.css +1 -1
  215. package/dist/ui/components/PageLoader.js +8 -3
  216. package/dist/ui/components/Select.d.ts +8 -1
  217. package/dist/ui/components/Select.js +2 -2
  218. package/dist/ui/components/SiteMark.d.ts +23 -0
  219. package/dist/ui/components/SiteMark.js +76 -0
  220. package/dist/ui/index.d.ts +2 -0
  221. package/dist/ui/index.js +1 -0
  222. package/dist/validation/schemas.d.ts +0 -2
  223. package/dist/validation/schemas.js +0 -2
  224. package/package.json +1 -1
  225. package/scripts/seed-cli.mjs +17 -15
@@ -330,7 +330,7 @@ export interface PrizeDrawItem {
330
330
  };
331
331
  condition: string;
332
332
  estimatedValue?: number;
333
- /** Per-slot price in paise for lottery mode. Lower price = higher weight = higher chance. */
333
+ /** Per-slot price in decimal rupees for lottery mode. Lower price = higher weight = higher chance. */
334
334
  prizeSlotPrice?: number;
335
335
  isWon: boolean;
336
336
  }
@@ -14,7 +14,7 @@ export interface ProductTemplateDocument {
14
14
  brand?: string;
15
15
  condition?: string;
16
16
  tags?: string[];
17
- /** Price hint in paise (INR × 100) */
17
+ /** Price hint in decimal rupees */
18
18
  price?: number;
19
19
  currency?: string;
20
20
  shippingPaidBy?: "buyer" | "seller";
@@ -40,6 +40,10 @@ export interface ProductItem {
40
40
  featured?: boolean;
41
41
  isPromoted?: boolean;
42
42
  allowShipBeforeEmiComplete?: boolean;
43
+ /** P-8 GST — buyer-facing tax rate on this product (%). Unset/0 = exempt. */
44
+ gstRate?: 0 | 5 | 12 | 18 | 28;
45
+ /** P-8 GST — Harmonized System of Nomenclature code for GST-compliant invoices. */
46
+ hsnCode?: string;
43
47
  printMeta?: ProductPrintMeta;
44
48
  currentBid?: number;
45
49
  availableQuantity?: number;
@@ -66,7 +66,7 @@ export const scammerFirestoreSchema = z.object({
66
66
  scamType: scamTypeSchema,
67
67
  scamPlatform: scamPlatformSchema,
68
68
  description: z.string(),
69
- amountLost: z.number().int().nonnegative().optional(),
69
+ amountLost: z.number().nonnegative().optional(),
70
70
  itemInvolved: z.string().optional(),
71
71
  evidence: z.array(z.string()),
72
72
  reportedBy: z.string(),
@@ -93,7 +93,7 @@ export const scammerIncidentFirestoreSchema = z.object({
93
93
  scamType: scamTypeSchema,
94
94
  scamPlatform: scamPlatformSchema,
95
95
  description: z.string(),
96
- amountLost: z.number().int().nonnegative().optional(),
96
+ amountLost: z.number().nonnegative().optional(),
97
97
  itemInvolved: z.string().optional(),
98
98
  evidence: z.array(z.string()),
99
99
  matchedPhones: z.array(z.string()).optional(),
@@ -208,7 +208,7 @@ export function Search({ isOpen, onClose, onSearch, onOpen, value, onChange, pla
208
208
  setIsInlineOpen(false);
209
209
  }
210
210
  };
211
- return (_jsxs(Row, { className: `relative ${className ?? ""}`, gap: "sm", children: [_jsxs(Row, { className: "relative flex-1", children: [_jsx("svg", { className: "absolute left-3 w-4 h-4 text-zinc-400 pointer-events-none", "aria-hidden": "true", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: SVG_PATH_SEARCH }) }), _jsx(Input, { ref: inputRef, bare: true, type: "search", value: query, onChange: (event) => handleInlineChange(event.target.value), onKeyDown: handleInlineKeyDown, onFocus: () => {
211
+ return (_jsxs(Row, { className: `relative ${className ?? ""}`, gap: "sm", children: [_jsxs(Row, { className: "relative flex-1 min-w-0", children: [_jsx("svg", { className: "absolute left-3 w-4 h-4 text-zinc-400 pointer-events-none", "aria-hidden": "true", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: SVG_PATH_SEARCH }) }), _jsx(Input, { ref: inputRef, bare: true, type: "search", value: query, onChange: (event) => handleInlineChange(event.target.value), onKeyDown: handleInlineKeyDown, onFocus: () => {
212
212
  if (inlineBlurRef.current)
213
213
  clearTimeout(inlineBlurRef.current);
214
214
  setIsInlineOpen(true);
@@ -216,7 +216,7 @@ export function Search({ isOpen, onClose, onSearch, onOpen, value, onChange, pla
216
216
  if (inlineBlurRef.current)
217
217
  clearTimeout(inlineBlurRef.current);
218
218
  inlineBlurRef.current = setTimeout(() => setIsInlineOpen(false), 120);
219
- }, placeholder: placeholder ?? labels.placeholder, className: "w-full rounded-lg border border-zinc-300 bg-[var(--appkit-color-surface)] pl-9 text-zinc-900 placeholder:text-zinc-400 focus:border-primary focus:ring-2 focus:ring-primary/20 border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] dark:text-white dark:placeholder:text-slate-500" }), query && (_jsx(Button, { rounded: "full", textColor: "faint", type: "button", variant: "ghost", onClick: handleClear, className: "absolute right-3 p-[0.125rem] hover:text-[var(--appkit-color-text-muted)] dark:hover:text-[var(--appkit-color-text)] transition-colors", "aria-label": labels.clearAriaLabel, children: _jsx("svg", { className: "w-4 h-4", "aria-hidden": "true", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) }))] }), resourceTypes && resourceTypes.length > 0 && (_jsx(Select, { options: resourceTypes, value: selectedType, onValueChange: handleTypeChange, "aria-label": labels.resourceTypeLabel ?? "Search in", className: "flex-shrink-0" })), deferred && (_jsx(Button, { rounded: "lg", paddingX: "sm", paddingY: "sm", type: "button", variant: "primary", onClick: handleDeferredSubmit, "aria-label": labels.ariaLabel, className: "flex-shrink-0", children: _jsx("svg", { className: "w-4 h-4", "aria-hidden": "true", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: SVG_PATH_SEARCH }) }) })), isInlineOpen && (filteredQuickLinks.length > 0 || query) && (_jsxs(Div, { rounded: "xl", border: "default", shadow: "lg", className: "absolute top-full left-0 right-0 mt-2 overflow-hidden bg-[var(--appkit-color-surface)] [z-index:var(--appkit-z-dropdown)]", onMouseDown: (event) => event.preventDefault(), children: [filteredQuickLinks.length > 0 && (_jsx(Ul, { children: filteredQuickLinks.map((link, index) => renderInlineQuickLink(link, index)) })), query && suggestionsLoading && (_jsx(Div, { padding: "inline", children: _jsx(Text, { variant: "secondary", size: "sm", children: labels.searching }) })), query &&
219
+ }, placeholder: placeholder ?? labels.placeholder, className: "w-full rounded-lg border border-zinc-300 bg-[var(--appkit-color-surface)] pl-9 text-zinc-900 placeholder:text-zinc-400 focus:border-primary focus:ring-2 focus:ring-primary/20 border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] dark:text-white dark:placeholder:text-slate-500" }), query && (_jsx(Button, { rounded: "full", textColor: "faint", type: "button", variant: "ghost", onClick: handleClear, className: "absolute right-3 p-[0.125rem] hover:text-[var(--appkit-color-text-muted)] dark:hover:text-[var(--appkit-color-text)] transition-colors", "aria-label": labels.clearAriaLabel, children: _jsx("svg", { className: "w-4 h-4", "aria-hidden": "true", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) }))] }), resourceTypes && resourceTypes.length > 0 && (_jsx(Select, { options: resourceTypes, value: selectedType, onValueChange: handleTypeChange, "aria-label": labels.resourceTypeLabel ?? "Search in", wrapperClassName: "flex-shrink-0" })), deferred && (_jsx(Button, { rounded: "lg", paddingX: "sm", paddingY: "sm", type: "button", variant: "primary", onClick: handleDeferredSubmit, "aria-label": labels.ariaLabel, className: "flex-shrink-0", children: _jsx("svg", { className: "w-4 h-4", "aria-hidden": "true", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: SVG_PATH_SEARCH }) }) })), isInlineOpen && (filteredQuickLinks.length > 0 || query) && (_jsxs(Div, { rounded: "xl", border: "default", shadow: "lg", className: "absolute top-full left-0 right-0 mt-2 overflow-hidden bg-[var(--appkit-color-surface)] [z-index:var(--appkit-z-dropdown)]", onMouseDown: (event) => event.preventDefault(), children: [filteredQuickLinks.length > 0 && (_jsx(Ul, { children: filteredQuickLinks.map((link, index) => renderInlineQuickLink(link, index)) })), query && suggestionsLoading && (_jsx(Div, { padding: "inline", children: _jsx(Text, { variant: "secondary", size: "sm", children: labels.searching }) })), query &&
220
220
  suggestions.slice(0, 5).map((suggestion, suggestionIndex) => {
221
221
  const itemIndex = inlineQuickLinkItems.length + suggestionIndex;
222
222
  const isActive = activeIndex === itemIndex;
@@ -292,7 +292,7 @@ export function Search({ isOpen, onClose, onSearch, onOpen, value, onChange, pla
292
292
  };
293
293
  if (!isOpen)
294
294
  return null;
295
- return (_jsx(Div, { border: "bottom-subtle", className: "animate-[fadeIn_150ms_ease-out]", surface: "default", shadow: "md", children: _jsxs(Div, { className: "container mx-auto md:py-[1rem] relative", padding: "inline", children: [_jsxs(Row, { gap: "sm", className: "md:gap-[0.75rem]", children: [_jsx(Div, { className: "flex-1 relative", children: _jsx(Input, { ref: inputRef, bare: true, type: "search", placeholder: placeholder ?? labels.placeholder, value: query, onChange: (event) => setQuery(event.target.value), onKeyDown: handleOverlayKeyDown, className: "w-full rounded-lg border border-zinc-300 bg-[var(--appkit-color-surface)] text-zinc-900 placeholder:text-zinc-400 focus:border-primary focus:ring-2 focus:ring-primary/20 border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] dark:text-white dark:placeholder:text-slate-500" }) }), resourceTypes && resourceTypes.length > 0 && (_jsx(Select, { options: resourceTypes, value: selectedType, onValueChange: handleTypeChange, "aria-label": labels.resourceTypeLabel ?? "Search in", className: "flex-shrink-0" })), _jsxs(Button, { gap: "md", onClick: handleOverlaySearch, variant: "primary", size: "md", className: "hidden sm:", children: [_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: SVG_PATH_SEARCH }) }), labels.title] }), _jsx(Button, { rounded: "xl", variant: "ghost", onClick: () => onClose?.(), className: "p-[var(--appkit-space-2-5)] md:p-[var(--appkit-space-3)] transition-colors text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface)] hover:text-zinc-700 text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface-elevated)] hover:text-[var(--appkit-color-text-muted)] flex-shrink-0", "aria-label": labels.closeAriaLabel, children: _jsx("svg", { className: "w-6 h-6 md:w-7 md:h-7", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })] }), (filteredSiteLinks.length > 0 || query) && (_jsxs(Stack, { className: "absolute top-full left-0 right-0 z-50 pt-[0.5rem] pb-[1rem]", padding: "x-md", gap: "sm", children: [filteredSiteLinks.length > 0 && !suggestionsLoading && (_jsxs(Div, { className: `${__O.hidden}`, rounded: "xl", shadow: "lg", surface: "default", border: "default", children: [_jsx(Div, { border: "default", className: "border-b", paddingY: "y-xs", paddingX: "x-md", children: _jsx(Text, { variant: "secondary", size: "xs", className: "uppercase tracking-wider", weight: "semibold", children: labels.quickLinks }) }), _jsx(Ul, { children: filteredSiteLinks.map((link) => {
295
+ return (_jsx(Div, { border: "bottom-subtle", className: "animate-[fadeIn_150ms_ease-out]", surface: "default", shadow: "md", children: _jsxs(Div, { className: "container mx-auto md:py-[1rem] relative", padding: "inline", children: [_jsxs(Row, { gap: "sm", className: "md:gap-[0.75rem]", children: [_jsx(Div, { className: "flex-1 relative min-w-0", children: _jsx(Input, { ref: inputRef, bare: true, type: "search", placeholder: placeholder ?? labels.placeholder, value: query, onChange: (event) => setQuery(event.target.value), onKeyDown: handleOverlayKeyDown, className: "w-full rounded-lg border border-zinc-300 bg-[var(--appkit-color-surface)] text-zinc-900 placeholder:text-zinc-400 focus:border-primary focus:ring-2 focus:ring-primary/20 border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] dark:text-white dark:placeholder:text-slate-500" }) }), resourceTypes && resourceTypes.length > 0 && (_jsx(Select, { options: resourceTypes, value: selectedType, onValueChange: handleTypeChange, "aria-label": labels.resourceTypeLabel ?? "Search in", wrapperClassName: "flex-shrink-0" })), _jsxs(Button, { gap: "md", onClick: handleOverlaySearch, variant: "primary", size: "md", className: "hidden sm:", children: [_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: SVG_PATH_SEARCH }) }), labels.title] }), _jsx(Button, { rounded: "xl", variant: "ghost", onClick: () => onClose?.(), className: "p-[var(--appkit-space-2-5)] md:p-[var(--appkit-space-3)] transition-colors text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface)] hover:text-zinc-700 text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface-elevated)] hover:text-[var(--appkit-color-text-muted)] flex-shrink-0", "aria-label": labels.closeAriaLabel, children: _jsx("svg", { className: "w-6 h-6 md:w-7 md:h-7", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })] }), (filteredSiteLinks.length > 0 || query) && (_jsxs(Stack, { className: "absolute top-full left-0 right-0 z-50 pt-[0.5rem] pb-[1rem]", padding: "x-md", gap: "sm", children: [filteredSiteLinks.length > 0 && !suggestionsLoading && (_jsxs(Div, { className: `${__O.hidden}`, rounded: "xl", shadow: "lg", surface: "default", border: "default", children: [_jsx(Div, { border: "default", className: "border-b", paddingY: "y-xs", paddingX: "x-md", children: _jsx(Text, { variant: "secondary", size: "xs", className: "uppercase tracking-wider", weight: "semibold", children: labels.quickLinks }) }), _jsx(Ul, { children: filteredSiteLinks.map((link) => {
296
296
  const Icon = link.icon;
297
297
  const itemIndex = quickLinkItems.findIndex((item) => item.link.href === link.href);
298
298
  const isActive = activeIndex === itemIndex;
@@ -4,8 +4,10 @@ import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit";
4
4
  import { sortBy } from "@mohasinac/appkit";
5
5
  import React, { useState, useCallback } from "react";
6
6
  import { useEntityDelete } from "../../../react/hooks/useEntityDelete";
7
+ import { useUrlTable } from "../../../react/hooks/useUrlTable";
7
8
  import { ConfirmDeleteModal, FilterChipGroup, ListingLayout, RowActionMenu } from "../../../ui";
8
9
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
10
+ import { TABLE_KEYS } from "../../../constants/table-keys";
9
11
  import { SELLER_AUCTION_STATUS_TABS } from "../../admin/constants/filter-tabs";
10
12
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
11
13
  import { ROUTES } from "../../../constants";
@@ -13,6 +15,12 @@ import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../..
13
15
  import { DataListingView } from "../../admin/components/DataListingView";
14
16
  import { SELLER_BULK_ACTIONS, ROW_ACTION_META } from "../../products/constants/action-defs";
15
17
  export function SellerAuctionsView({ children, onEditClick, onDelete, onBulkDelete, ...props }) {
18
+ // Independent of DataListingView's own internal useUrlTable() — both read
19
+ // and write the same URL, useUrlTable has no local state, so they stay in
20
+ // sync (same pattern SellerProductsView already uses for its "Show sold"
21
+ // toggle).
22
+ const toggleTable = useUrlTable({});
23
+ const showEnded = toggleTable.get(TABLE_KEYS.SHOW_ENDED) === "true";
16
24
  const [deleteTargetId, setDeleteTargetId] = useState(null);
17
25
  const { deletingId, handleDelete: performDelete } = useEntityDelete({
18
26
  endpoint: SELLER_ENDPOINTS.PRODUCT_BY_ID,
@@ -38,7 +46,7 @@ export function SellerAuctionsView({ children, onEditClick, onDelete, onBulkDele
38
46
  title: "Auctions",
39
47
  searchPlaceholder: "Search auctions by product name",
40
48
  emptyLabel: "No auctions found",
41
- filterKeys: ["status"],
49
+ filterKeys: ["status", TABLE_KEYS.SHOW_ENDED],
42
50
  defaultSort: sortBy("auctionEndDate", "ASC"),
43
51
  queryKey: ["seller", "auctions", "listing"],
44
52
  endpoint: SELLER_ENDPOINTS.AUCTIONS,
@@ -61,7 +69,24 @@ export function SellerAuctionsView({ children, onEditClick, onDelete, onBulkDele
61
69
  getTotal: (response, mappedRows) => typeof response.meta?.total === "number"
62
70
  ? response.meta.total
63
71
  : mappedRows.length,
64
- buildFilters: (state) => state.status && state.status !== "All" ? sieveFilter("status", SIEVE_OP.EQ, state.status) : undefined,
72
+ buildFilters: (state) => {
73
+ // Previously missing entirely — the seller Auctions tab silently
74
+ // listed the entire seller catalog, not just auctions.
75
+ const parts = ["listingType==auction"];
76
+ if (state.status && state.status !== "All")
77
+ parts.push(sieveFilter("status", SIEVE_OP.EQ, state.status));
78
+ if (state[TABLE_KEYS.SHOW_ENDED] !== "true") {
79
+ parts.push(sieveFilter("auctionEndDate", SIEVE_OP.GTE, new Date().toISOString()));
80
+ }
81
+ return parts.join(",");
82
+ },
83
+ toggles: [
84
+ {
85
+ label: "Show ended",
86
+ active: showEnded,
87
+ onChange: (next) => toggleTable.set(TABLE_KEYS.SHOW_ENDED, next ? "true" : ""),
88
+ },
89
+ ],
65
90
  // Rule #7: bulk-action array sourced from the SELLER_BULK_ACTIONS preset.
66
91
  buildBulkActions: onBulkDelete
67
92
  ? (selection) => SELLER_BULK_ACTIONS.auctions.map((id) => ({
@@ -151,7 +151,7 @@ function OrderDetailDrawer({ orderId, apiBase, onClose, }) {
151
151
  };
152
152
  const addr = order?.shippingAddress ?? {};
153
153
  const addrLine = [addr.addressLine1, addr.city, addr.state, addr.pincode].filter(Boolean).join(", ");
154
- return (_jsxs(SideDrawer, { isOpen: true, title: `Order ${order?.id ?? orderId}`, onClose: onClose, children: [loading && (_jsx(Row, { align: "center", justify: "center", padding: "y-4xl", children: _jsx(Div, { className: "h-6 w-6 animate-spin border-2 border-[var(--appkit-color-primary)] border-t-transparent", rounded: "full" }) })), fetchError && (_jsx(Div, { textSize: "sm", className: "mx-4 mt-4 border border-error/20", color: "error", surface: "danger-surface", padding: "inline", rounded: "lg", children: fetchError })), order && !loading && (_jsxs(Stack, { gap: "none", children: [_jsxs(Stack, { className: `flex-1 ${__O.yAuto}`, gap: "5", padding: "md", children: [_jsxs(Row, { align: "center", justify: "between", children: [_jsx(Badge, { variant: STATUS_BADGE_VARIANT[order.status?.toUpperCase()] ?? "default", children: order.status ?? "Unknown" }), _jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text-secondary)]", children: toRelativeDate(order.createdAt) })] }), (order.items ?? []).length > 0 && (_jsxs(Div, { children: [_jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text-primary)] mb-2", weight: "semibold", children: "Items" }), _jsx(Div, { className: "divide-y divide-[var(--appkit-color-border)] divide-[var(--appkit-color-border)] border border-[var(--appkit-color-border)]", rounded: "lg", children: (order.items ?? []).map((item, i) => (_jsxs(Row, { paddingY: "y-xs-tall", padding: "x-sm", align: "center", justify: "between", gap: "3", children: [_jsxs(Div, { className: "min-w-0", children: [_jsx(Text, { size: "sm", className: "truncate", weight: "medium", children: item.title ?? item.productId ?? "Item" }), _jsxs(Text, { size: "xs", className: "text-[var(--appkit-color-text-secondary)]", children: ["Qty: ", item.quantity ?? 1] })] }), _jsx(Text, { size: "sm", className: "shrink-0", weight: "medium", children: toCurrency(item.price ?? 0) })] }, i))) })] })), _jsxs(Row, { surface: "muted", padding: "inline", align: "center", justify: "between", rounded: "lg", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Total" }), _jsx(Text, { size: "sm", className: "text-[var(--appkit-color-primary)]", weight: "bold", children: toCurrency(order.totalAmount ?? 0) })] }), addrLine && (_jsxs(Div, { children: [_jsx(Text, { size: "sm", className: "mb-1", weight: "semibold", children: "Shipping address" }), _jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text-secondary)]", children: [String(addr.fullName ?? ""), addrLine].filter(Boolean).join(" · ") })] })), order.paymentMethod && (_jsxs(Div, { children: [_jsx(Text, { size: "sm", className: "mb-1", weight: "semibold", children: "Payment" }), _jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text-secondary)]", transform: "capitalize", children: order.paymentMethod })] })), order.emiEnabled && (_jsxs(Div, { className: "border-t border-[var(--appkit-color-border)]", padding: "t-md", children: [_jsxs(Row, { align: "center", justify: "between", className: "mb-2", children: [_jsxs(Text, { size: "sm", weight: "semibold", children: ["EMI plan ", order.emiTenureMonths ? `· ${order.emiTenureMonths} months` : ""] }), _jsx(Badge, { variant: order.emiComplete ? "success" : "warning", children: order.emiComplete ? "Fully paid" : "In progress" })] }), _jsxs(Row, { align: "center", justify: "between", className: "mb-2", children: [_jsx(Text, { size: "xs", color: "muted", children: "Token collected" }), _jsx(Text, { size: "xs", weight: "medium", children: toCurrency(order.emiTokenAmount ?? 0) })] }), _jsxs(Row, { align: "center", justify: "between", className: "mb-3", children: [_jsx(Text, { size: "xs", color: "muted", children: "Remaining balance" }), _jsx(Text, { size: "xs", weight: "medium", children: toCurrency(order.emiRemainingBalance ?? 0) })] }), _jsx(Div, { className: "divide-y divide-[var(--appkit-color-border)] border border-[var(--appkit-color-border)]", rounded: "lg", children: (order.emiInstallments ?? []).map((inst) => (_jsxs(Row, { paddingY: "y-xs-tall", padding: "x-sm", align: "center", justify: "between", gap: "3", children: [_jsxs(Div, { className: "min-w-0", children: [_jsxs(Text, { size: "sm", weight: "medium", children: ["Installment ", inst.index, " \u00B7 ", toCurrency(inst.amount)] }), _jsx(Text, { size: "xs", color: "muted", children: inst.status === "paid" && inst.paidAt
154
+ return (_jsxs(SideDrawer, { isOpen: true, title: `Order ${order?.id ?? orderId}`, onClose: onClose, children: [loading && (_jsx(Row, { align: "center", justify: "center", padding: "y-4xl", children: _jsx(Div, { className: "h-6 w-6 animate-spin border-2 border-[var(--appkit-color-primary)] border-t-transparent", rounded: "full" }) })), fetchError && (_jsx(Div, { textSize: "sm", className: "mx-4 mt-4 border border-error/20", color: "error", surface: "danger-surface", padding: "inline", rounded: "lg", children: fetchError })), order && !loading && (_jsxs(Stack, { gap: "none", children: [_jsxs(Stack, { className: `flex-1 ${__O.yAuto}`, gap: "5", padding: "md", children: [_jsxs(Row, { align: "center", justify: "between", children: [_jsx(Badge, { variant: STATUS_BADGE_VARIANT[order.status?.toUpperCase()] ?? "default", children: order.status ?? "Unknown" }), _jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text-secondary)]", children: toRelativeDate(order.createdAt) })] }), (order.items ?? []).length > 0 && (_jsxs(Div, { children: [_jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text-primary)] mb-2", weight: "semibold", children: "Items" }), _jsx(Div, { className: "divide-y divide-[var(--appkit-color-border)] divide-[var(--appkit-color-border)] border border-[var(--appkit-color-border)]", rounded: "lg", children: (order.items ?? []).map((item, i) => (_jsxs(Row, { paddingY: "y-xs-tall", padding: "x-sm", align: "center", justify: "between", gap: "3", children: [_jsxs(Div, { className: "min-w-0", children: [_jsx(Text, { size: "sm", className: "truncate", weight: "medium", children: item.title ?? item.productId ?? "Item" }), _jsxs(Text, { size: "xs", className: "text-[var(--appkit-color-text-secondary)]", children: ["Qty: ", item.quantity ?? 1] })] }), _jsx(Text, { size: "sm", className: "shrink-0", weight: "medium", children: toCurrency(item.price ?? 0) })] }, i))) })] })), order.giftWrapAddon && (_jsxs(Div, { className: "border border-[var(--appkit-color-primary-200)] dark:border-[var(--appkit-color-primary-800)]", surface: "subtle", padding: "inline", rounded: "lg", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "\uD83C\uDF81 Gift wrap requested" }), order.giftWrapMessage && (_jsxs(Text, { size: "sm", className: "mt-1 text-[var(--appkit-color-text-secondary)]", children: ["\u201C", order.giftWrapMessage, "\u201D"] }))] })), _jsxs(Row, { surface: "muted", padding: "inline", align: "center", justify: "between", rounded: "lg", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Total" }), _jsx(Text, { size: "sm", className: "text-[var(--appkit-color-primary)]", weight: "bold", children: toCurrency(order.totalAmount ?? 0) })] }), addrLine && (_jsxs(Div, { children: [_jsx(Text, { size: "sm", className: "mb-1", weight: "semibold", children: "Shipping address" }), _jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text-secondary)]", children: [String(addr.fullName ?? ""), addrLine].filter(Boolean).join(" · ") })] })), order.paymentMethod && (_jsxs(Div, { children: [_jsx(Text, { size: "sm", className: "mb-1", weight: "semibold", children: "Payment" }), _jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text-secondary)]", transform: "capitalize", children: order.paymentMethod })] })), order.emiEnabled && (_jsxs(Div, { className: "border-t border-[var(--appkit-color-border)]", padding: "t-md", children: [_jsxs(Row, { align: "center", justify: "between", className: "mb-2", children: [_jsxs(Text, { size: "sm", weight: "semibold", children: ["EMI plan ", order.emiTenureMonths ? `· ${order.emiTenureMonths} months` : ""] }), _jsx(Badge, { variant: order.emiComplete ? "success" : "warning", children: order.emiComplete ? "Fully paid" : "In progress" })] }), _jsxs(Row, { align: "center", justify: "between", className: "mb-2", children: [_jsx(Text, { size: "xs", color: "muted", children: "Token collected" }), _jsx(Text, { size: "xs", weight: "medium", children: toCurrency(order.emiTokenAmount ?? 0) })] }), _jsxs(Row, { align: "center", justify: "between", className: "mb-3", children: [_jsx(Text, { size: "xs", color: "muted", children: "Remaining balance" }), _jsx(Text, { size: "xs", weight: "medium", children: toCurrency(order.emiRemainingBalance ?? 0) })] }), _jsx(Div, { className: "divide-y divide-[var(--appkit-color-border)] border border-[var(--appkit-color-border)]", rounded: "lg", children: (order.emiInstallments ?? []).map((inst) => (_jsxs(Row, { paddingY: "y-xs-tall", padding: "x-sm", align: "center", justify: "between", gap: "3", children: [_jsxs(Div, { className: "min-w-0", children: [_jsxs(Text, { size: "sm", weight: "medium", children: ["Installment ", inst.index, " \u00B7 ", toCurrency(inst.amount)] }), _jsx(Text, { size: "xs", color: "muted", children: inst.status === "paid" && inst.paidAt
155
155
  ? `Paid ${toRelativeDate(inst.paidAt)}`
156
156
  : inst.dueDate
157
157
  ? `Due ${toRelativeDate(inst.dueDate)}`
@@ -309,7 +309,7 @@ export function SellerOrdersView({ orderDetailApiBase = SELLER_ENDPOINTS.ORDERS,
309
309
  const ids = selection.selectedIds.join(",");
310
310
  void dispatch({
311
311
  type: "NAVIGATE",
312
- href: `${String(ROUTES.STORE.INVENTORY_PRINT)}?type=order&ids=${ids}&autoprint=1`,
312
+ href: `${String(ROUTES.STORE.PRINT_CENTER)}?type=order&ids=${ids}&autoprint=1`,
313
313
  });
314
314
  }, [selection.selectedIds, dispatch]);
315
315
  const handleSetLocation = useCallback(async (loc) => {
@@ -8,7 +8,7 @@ import { Badge, Div, FilterChipGroup, ListingLayout, Row, RowActionMenu, SideDra
8
8
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
9
9
  import { ADMIN_PAYOUT_STATUS_TABS } from "../../admin/constants/filter-tabs";
10
10
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
11
- import { buildBulkAction } from "../../../_internal/shared/actions/bulk-helpers";
11
+ import { SELLER_BULK_ACTIONS, ROW_ACTION_META } from "../../products/constants/action-defs";
12
12
  import { apiClient } from "../../../http";
13
13
  import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../../admin/hooks/useAdminListingData";
14
14
  import { DataListingView } from "../../admin/components/DataListingView";
@@ -77,7 +77,7 @@ export function SellerPayoutsView({ children, ...props }) {
77
77
  title: "Payouts",
78
78
  searchPlaceholder: "Search payouts by payout # or amount",
79
79
  emptyLabel: "No payouts found",
80
- filterKeys: ["status"],
80
+ filterKeys: ["status", "showAllPayouts"],
81
81
  defaultSort: sortBy("createdAt", "DESC"),
82
82
  queryKey: ["seller", "payouts", "listing"],
83
83
  endpoint: SELLER_ENDPOINTS.PAYOUTS,
@@ -102,17 +102,28 @@ export function SellerPayoutsView({ children, ...props }) {
102
102
  getTotal: (response, mappedRows) => typeof response.meta?.total === "number"
103
103
  ? response.meta.total
104
104
  : mappedRows.length,
105
- buildFilters: (state) => state.status && state.status !== "All" ? sieveFilter("status", SIEVE_OP.EQ, state.status) : undefined,
106
- // Rule #7: bulk-action array sourced from the CTA registry, not inline objects.
107
- buildBulkActions: (selection) => [
108
- buildBulkAction(ACTIONS.STORE["export-payout"], () => {
105
+ buildFilters: (state) => {
106
+ // An explicit status chip always wins. Otherwise, hide already-paid
107
+ // payouts by default — mirrors the isSold-hide-by-default precedent.
108
+ // Pipe-joined EQ (not !=) see AdminPayoutsView.tsx for why.
109
+ if (state.status && state.status !== "All")
110
+ return sieveFilter("status", SIEVE_OP.EQ, state.status);
111
+ return state.showAllPayouts !== "true"
112
+ ? sieveFilter("status", SIEVE_OP.EQ, "pending|processing|failed|cancelled")
113
+ : undefined;
114
+ },
115
+ // Rule #7: bulk-action array sourced from the SELLER_BULK_ACTIONS preset.
116
+ buildBulkActions: (selection) => SELLER_BULK_ACTIONS.payouts.map((id) => ({
117
+ id,
118
+ label: ROW_ACTION_META[id].label,
119
+ onClick: () => {
109
120
  const rows = selection.selectedIds
110
- .map((id) => rawByIdRef.current[id])
121
+ .map((rowId) => rawByIdRef.current[rowId])
111
122
  .filter((r) => Boolean(r));
112
123
  handleExport(rows);
113
124
  selection.clearSelection();
114
- }),
115
- ],
125
+ },
126
+ })),
116
127
  renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
117
128
  {
118
129
  label: ACTIONS.STORE["view-payout"].label,
@@ -123,7 +134,10 @@ export function SellerPayoutsView({ children, ...props }) {
123
134
  onClick: () => handleExport([rawByIdRef.current[row.id] ?? { id: row.id }]),
124
135
  },
125
136
  ] })),
126
- renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_PAYOUT_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) })),
137
+ renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_PAYOUT_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Paid payouts", tabs: [
138
+ { id: "", label: "Hide paid" },
139
+ { id: "true", label: "Show all" },
140
+ ], value: pendingFilters.showAllPayouts ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, showAllPayouts: id })), allId: "" })] })),
127
141
  };
128
142
  const expectedByDate = selectedPayout?.requestedAt
129
143
  ? new Date(new Date(selectedPayout.requestedAt).getTime() + PAYOUT_CYCLE_DAYS * 24 * 60 * 60 * 1000)
@@ -36,13 +36,13 @@ const PRE_ORDER_COLUMNS = [
36
36
  header: "Status",
37
37
  className: "w-28",
38
38
  render: (row) => {
39
- const variant = row.status === "active"
39
+ // Real ProductStatus is draft|published|in_review|archived "active"
40
+ // fixed to "published"; "cancelled" is not a stored status value.
41
+ const variant = row.status === "published"
40
42
  ? "success"
41
43
  : row.status === "draft"
42
44
  ? "default"
43
- : row.status === "cancelled"
44
- ? "danger"
45
- : "warning";
45
+ : "warning";
46
46
  return _jsx(Badge, { variant: variant, children: row.status });
47
47
  },
48
48
  },
@@ -107,6 +107,14 @@ export function SellerPreOrdersView({ children, onDelete, ...props }) {
107
107
  };
108
108
  }),
109
109
  getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
110
+ // No "Show closed" toggle here (unlike Auctions/Prize Draws): the public
111
+ // PreOrdersIndexListing equivalent filters on stockQuantity>0, a range
112
+ // filter, which cannot be combined with this view's title/
113
+ // preOrderDeliveryDate/createdAt sort options without violating
114
+ // Firestore's orderBy-must-match-the-range-field rule (confirmed by
115
+ // appkit/scripts/audit-listing-indices.mjs QUERY_UNSATISFIABLE). Fixing
116
+ // this properly would mean constraining sort choices per toggle state,
117
+ // out of scope for this pass — left as a real gap, not silently broken.
110
118
  buildFilters: (state) => {
111
119
  const status = state.status && state.status !== "All" ? sieveFilter("status", SIEVE_OP.EQ, state.status) : null;
112
120
  return ["listingType==pre-order", status].filter(Boolean).join(",");
@@ -4,20 +4,24 @@ import { Row, SIEVE_OP, Stack, sieveFilter } from "@mohasinac/appkit";
4
4
  import { sortBy } from "@mohasinac/appkit";
5
5
  import React, { useState, useCallback } from "react";
6
6
  import { useEntityDelete } from "../../../react/hooks/useEntityDelete";
7
+ import { useUrlTable } from "../../../react/hooks/useUrlTable";
7
8
  import { Badge, ConfirmDeleteModal, Div, FilterChipGroup, ListingLayout, RowActionMenu, Span, Text } from "../../../ui";
8
9
  import { MediaImage } from "../../media/MediaImage";
9
10
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
11
+ import { TABLE_KEYS } from "../../../constants/table-keys";
10
12
  import { SELLER_PRIZE_DRAW_STATUS_TABS } from "../../admin/constants/filter-tabs";
11
13
  import { ROUTES } from "../../../constants";
12
14
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
13
15
  import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../../admin/hooks/useAdminListingData";
14
16
  import { DataListingView } from "../../admin/components/DataListingView";
15
17
  import { useActionDispatch } from "../../../react/hooks/use-action-dispatch";
18
+ // Real ProductStatus is draft|published|in_review|archived — "active"
19
+ // fixed to "published"; "ended"/"cancelled" removed since status never
20
+ // holds those values (ended is derived from prizeRevealWindowEnd, shown
21
+ // via the "Show closed" toolbar toggle below).
16
22
  const STATUS_VARIANT = {
17
- active: "success",
23
+ published: "success",
18
24
  draft: "default",
19
- ended: "secondary",
20
- cancelled: "danger",
21
25
  };
22
26
  const PRIZE_DRAW_COLUMNS = [
23
27
  {
@@ -58,6 +62,10 @@ const PRIZE_DRAW_COLUMNS = [
58
62
  ];
59
63
  export function SellerPrizeDrawsView({ children, onDelete, ...props }) {
60
64
  const dispatch = useActionDispatch();
65
+ // Independent of DataListingView's own internal useUrlTable() — see the
66
+ // same pattern in SellerAuctionsView.tsx.
67
+ const toggleTable = useUrlTable({});
68
+ const showClosed = toggleTable.get(TABLE_KEYS.SHOW_CLOSED) === "true";
61
69
  const [deleteTargetId, setDeleteTargetId] = useState(null);
62
70
  const { deletingId, handleDelete: performDelete } = useEntityDelete({
63
71
  endpoint: SELLER_ENDPOINTS.PRODUCT_BY_ID,
@@ -77,7 +85,7 @@ export function SellerPrizeDrawsView({ children, onDelete, ...props }) {
77
85
  title: "Prize Draws",
78
86
  searchPlaceholder: "Search prize draws by name…",
79
87
  emptyLabel: "No prize draws listed yet",
80
- filterKeys: ["status"],
88
+ filterKeys: ["status", TABLE_KEYS.SHOW_CLOSED],
81
89
  defaultSort: sortBy("createdAt", "DESC"),
82
90
  queryKey: ["seller", "prize-draws", "listing"],
83
91
  endpoint: SELLER_ENDPOINTS.PRODUCTS,
@@ -106,8 +114,18 @@ export function SellerPrizeDrawsView({ children, onDelete, ...props }) {
106
114
  getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
107
115
  buildFilters: (state) => {
108
116
  const status = state.status && state.status !== "All" ? sieveFilter("status", SIEVE_OP.EQ, state.status) : null;
109
- return ["listingType==prize-draw", status].filter(Boolean).join(",");
117
+ const closed = state[TABLE_KEYS.SHOW_CLOSED] !== "true"
118
+ ? sieveFilter("prizeRevealWindowEnd", SIEVE_OP.GTE, new Date().toISOString())
119
+ : null;
120
+ return ["listingType==prize-draw", status, closed].filter(Boolean).join(",");
110
121
  },
122
+ toggles: [
123
+ {
124
+ label: "Show closed",
125
+ active: showClosed,
126
+ onChange: (next) => toggleTable.set(TABLE_KEYS.SHOW_CLOSED, next ? "true" : ""),
127
+ },
128
+ ],
111
129
  rowHrefTemplate: String(ROUTES.STORE.PRIZE_DRAWS_EDIT("{id}")),
112
130
  renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
113
131
  {
@@ -19,6 +19,8 @@ export interface SellerProductDraft {
19
19
  isPromoted?: boolean;
20
20
  isNew?: boolean;
21
21
  isOnSale?: boolean;
22
+ allowOffers?: boolean;
23
+ minOfferPercent?: number;
22
24
  shippingPaidBy?: "buyer" | "seller";
23
25
  pickupAddressId?: string;
24
26
  insurance?: boolean;
@@ -180,7 +180,7 @@ const TYPE_SPECIFIC_STEPS = {
180
180
  function StepPricing({ values, onChange, listingType, }) {
181
181
  const listingPlugin = pluginForMode(listingType);
182
182
  const priceLabel = listingPlugin.priceLabel;
183
- return (_jsxs(Stack, { gap: "md", children: [_jsx(Alert, { variant: "info", children: "All prices in Indian Rupees (\u20B9)." }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "price", label: priceLabel, type: "number", value: toRupeesString(values.price), onChange: (v) => onChange({ price: fromRupeesString(v) }), placeholder: "999" }), _jsx(FormField, { name: "compareAtPrice", label: "Compare-at Price (\u20B9, optional)", type: "number", value: toRupeesString(values.compareAtPrice), onChange: (v) => onChange({ compareAtPrice: v ? fromRupeesString(v) : undefined }), placeholder: "1299", helpText: "Original price shown as strikethrough" })] }), listingPlugin.showsStockQuantity && (_jsx(FormField, { name: "stockQuantity", label: "Stock Quantity", type: "number", value: String(values.stockQuantity ?? ""), onChange: (v) => onChange({ stockQuantity: Number(v) }), placeholder: "1" })), _jsxs(FormGroup, { columns: 2, children: [_jsx(Toggle, { checked: !!values.featured, onChange: (checked) => onChange({ featured: checked }), label: "Mark as Featured" }), _jsx(Toggle, { checked: !!values.isNew, onChange: (checked) => onChange({ isNew: checked }), label: "Mark as New Arrival" })] })] }));
183
+ return (_jsxs(Stack, { gap: "md", children: [_jsx(Alert, { variant: "info", children: "All prices in Indian Rupees (\u20B9)." }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "price", label: priceLabel, type: "number", value: toRupeesString(values.price), onChange: (v) => onChange({ price: fromRupeesString(v) }), placeholder: "999" }), _jsx(FormField, { name: "compareAtPrice", label: "Compare-at Price (\u20B9, optional)", type: "number", value: toRupeesString(values.compareAtPrice), onChange: (v) => onChange({ compareAtPrice: v ? fromRupeesString(v) : undefined }), placeholder: "1299", helpText: "Original price shown as strikethrough" })] }), listingPlugin.showsStockQuantity && (_jsx(FormField, { name: "stockQuantity", label: "Stock Quantity", type: "number", value: String(values.stockQuantity ?? ""), onChange: (v) => onChange({ stockQuantity: Number(v) }), placeholder: "1" })), _jsxs(FormGroup, { columns: 2, children: [_jsx(Toggle, { checked: !!values.featured, onChange: (checked) => onChange({ featured: checked }), label: "Mark as Featured" }), _jsx(Toggle, { checked: !!values.isNew, onChange: (checked) => onChange({ isNew: checked }), label: "Mark as New Arrival" })] }), _jsx(Toggle, { checked: !!values.allowOffers, onChange: (checked) => onChange({ allowOffers: checked, minOfferPercent: checked ? values.minOfferPercent ?? 50 : undefined }), label: "Allow buyer offers" }), values.allowOffers && (_jsx(FormField, { name: "minOfferPercent", label: "Minimum offer (% of price)", type: "number", value: String(values.minOfferPercent ?? ""), onChange: (v) => onChange({ minOfferPercent: Number(v) }), placeholder: "50" }))] }));
184
184
  }
185
185
  // ── Step: Shipping ────────────────────────────────────────────────────────
186
186
  function StepShipping({ values, onChange, renderAddressSelector, }) {
@@ -65,7 +65,7 @@ function TypeDropdown({ active, onChange, }) {
65
65
  flags["digital-code"] && { value: "digital-code", label: "Digital Code" },
66
66
  flags.live && { value: "live", label: "Live" },
67
67
  ].filter(Boolean);
68
- return (_jsxs(Row, { paddingX: "x-sm-lg-md", border: "bottom", padding: "y-xs", gap: "sm", children: [_jsx(Text, { className: "tracking-wide text-[var(--appkit-color-text-muted)]", size: "xs", weight: "semibold", transform: "uppercase", children: "Listing type" }), _jsx(Select, { options: options, value: active, onValueChange: (v) => onChange(v), className: "sm:max-w-xs", "aria-label": "Filter by listing type" })] }));
68
+ return (_jsxs(Row, { paddingX: "x-sm-lg-md", border: "bottom", padding: "y-xs", gap: "sm", children: [_jsx(Text, { className: "tracking-wide text-[var(--appkit-color-text-muted)]", size: "xs", weight: "semibold", transform: "uppercase", children: "Listing type" }), _jsx(Select, { options: options, value: active, onValueChange: (v) => onChange(v), wrapperClassName: "sm:max-w-xs", "aria-label": "Filter by listing type" })] }));
69
69
  }
70
70
  const PRODUCT_COLUMNS = [
71
71
  {
@@ -90,13 +90,15 @@ const PRODUCT_COLUMNS = [
90
90
  header: "Status",
91
91
  className: "w-28",
92
92
  render: (row) => {
93
- const variant = row.status === "active"
93
+ // Real ProductStatus is draft|published|in_review|archived "active"
94
+ // and "sold" are not stored status values (sold is tracked separately
95
+ // via isSold, shown via the "Show sold" toolbar toggle, not this
96
+ // column), so those two branches never matched and were dead code.
97
+ const variant = row.status === "published"
94
98
  ? "success"
95
99
  : row.status === "draft"
96
100
  ? "default"
97
- : row.status === "sold"
98
- ? "secondary"
99
- : "danger";
101
+ : "danger";
100
102
  return (_jsx(Badge, { variant: variant, children: row.status }));
101
103
  },
102
104
  },
@@ -224,7 +226,7 @@ export function SellerProductsView({ onDeleteProduct, onCreateClick, children, .
224
226
  const ids = selection.selectedIds.join(",");
225
227
  void dispatch({
226
228
  type: "NAVIGATE",
227
- href: `${String(ROUTES.STORE.INVENTORY_PRINT)}?type=product&ids=${ids}&autoprint=1`,
229
+ href: `${String(ROUTES.STORE.PRINT_CENTER)}?type=product&ids=${ids}&autoprint=1`,
228
230
  });
229
231
  }, [selection.selectedIds, dispatch]);
230
232
  const handleSetLocation = useCallback(async (loc) => {
@@ -2,11 +2,18 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { normalizeError } from "../../../errors/normalize";
4
4
  import { useCallback, useEffect, useMemo, useState } from "react";
5
- import { Alert, Badge, BulkActionBar, Button, Checkbox, Div, Modal, Row, Select, SideDrawer, Span, Stack, Text, Textarea } from "../../../ui";
5
+ import { Alert, Badge, BulkActionBar, Button, Checkbox, Div, Input, Modal, Row, Select, SideDrawer, Span, Stack, Text, Textarea } from "../../../ui";
6
6
  import { StackedViewShell } from "../../../ui";
7
7
  import { useBottomActions } from "../../layout";
8
8
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
9
9
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
10
+ import { sortBy } from "../../../constants/sort";
11
+ const SORT_OPTIONS = [
12
+ { value: sortBy("createdAt", "DESC"), label: "Newest" },
13
+ { value: sortBy("createdAt", "ASC"), label: "Oldest" },
14
+ { value: sortBy("rating", "DESC"), label: "Highest rating" },
15
+ { value: sortBy("rating", "ASC"), label: "Lowest rating" },
16
+ ];
10
17
  const __P = {
11
18
  p3: "p-[var(--appkit-space-3)]",
12
19
  p4: "p-[var(--appkit-space-4)]",
@@ -32,6 +39,8 @@ export function SellerReviewsView({ reviewsApiBase = SELLER_ENDPOINTS.REVIEWS, r
32
39
  // Filters
33
40
  const [rating, setRating] = useState("");
34
41
  const [replied, setReplied] = useState("");
42
+ const [sort, setSort] = useState(SORT_OPTIONS[0].value);
43
+ const [search, setSearch] = useState("");
35
44
  const [page, setPage] = useState(1);
36
45
  // Reply drawer
37
46
  const [replyTarget, setReplyTarget] = useState(null);
@@ -101,7 +110,7 @@ export function SellerReviewsView({ reviewsApiBase = SELLER_ENDPOINTS.REVIEWS, r
101
110
  setLoading(true);
102
111
  setError(null);
103
112
  try {
104
- const params = new URLSearchParams({ page: String(page), pageSize: "20" });
113
+ const params = new URLSearchParams({ page: String(page), pageSize: "20", sorts: sort });
105
114
  if (rating)
106
115
  params.set("rating", rating);
107
116
  if (replied)
@@ -120,7 +129,19 @@ export function SellerReviewsView({ reviewsApiBase = SELLER_ENDPOINTS.REVIEWS, r
120
129
  finally {
121
130
  setLoading(false);
122
131
  }
123
- }, [reviewsApiBase, rating, replied, page]);
132
+ }, [reviewsApiBase, rating, replied, sort, page]);
133
+ // The API route has no ?q= support (only rating/replied/sorts) — filter
134
+ // the fetched page client-side, same category of post-fetch narrowing the
135
+ // route itself already does for `replied`.
136
+ const visibleReviews = useMemo(() => {
137
+ const q = search.trim().toLowerCase();
138
+ if (!q)
139
+ return reviews;
140
+ return reviews.filter((r) => r.productTitle?.toLowerCase().includes(q) ||
141
+ r.userName?.toLowerCase().includes(q) ||
142
+ r.comment?.toLowerCase().includes(q) ||
143
+ r.title?.toLowerCase().includes(q));
144
+ }, [reviews, search]);
124
145
  useEffect(() => { fetchReviews(); }, [fetchReviews]);
125
146
  useEffect(() => { fetchReviewsRef.current = fetchReviews; }, [fetchReviews, fetchReviewsRef]);
126
147
  const openReply = (review) => {
@@ -161,7 +182,7 @@ export function SellerReviewsView({ reviewsApiBase = SELLER_ENDPOINTS.REVIEWS, r
161
182
  },
162
183
  ] } } : {});
163
184
  return (_jsxs(_Fragment, { children: [_jsx(StackedViewShell, { portal: "seller", title: "Reviews", sections: [
164
- _jsxs(Stack, { gap: "lg", children: [_jsxs(Row, { align: "center", gap: "3", wrap: true, children: [_jsx(Select, { value: rating, onChange: (e) => { setRating(e.target.value); setPage(1); }, "aria-label": "Filter by rating", options: [
185
+ _jsxs(Stack, { gap: "lg", children: [_jsxs(Row, { align: "center", gap: "3", wrap: true, children: [_jsx(Input, { value: search, onChange: (e) => setSearch(e.target.value), placeholder: "Search product, reviewer, or comment\u2026", "aria-label": "Search reviews", className: "min-w-[220px]" }), _jsx(Select, { value: rating, onChange: (e) => { setRating(e.target.value); setPage(1); }, "aria-label": "Filter by rating", options: [
165
186
  { value: "", label: "All ratings" },
166
187
  { value: "5", label: "5 stars" },
167
188
  { value: "4", label: "4 stars" },
@@ -172,12 +193,12 @@ export function SellerReviewsView({ reviewsApiBase = SELLER_ENDPOINTS.REVIEWS, r
172
193
  { value: "", label: "All reply statuses" },
173
194
  { value: "true", label: "Store replied" },
174
195
  { value: "false", label: "Awaiting reply" },
175
- ] }), meta && (_jsxs(Text, { className: "text-[var(--appkit-color-text-muted)] ml-auto", size: "sm", 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: [
196
+ ] }), _jsx(Select, { value: sort, onChange: (e) => { setSort(e.target.value); setPage(1); }, "aria-label": "Sort reviews", options: SORT_OPTIONS }), meta && (_jsxs(Text, { className: "text-[var(--appkit-color-text-muted)] ml-auto", size: "sm", 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: [
176
197
  {
177
198
  id: "bulk-reply",
178
199
  label: "Reply to selected",
179
200
  onClick: () => setBulkReplyOpen(true),
180
201
  },
181
- ] })), loading ? (_jsx(Div, { className: "text-center", padding: "y-xl", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Loading reviews\u2026" }) })) : reviews.length === 0 ? (_jsx(Div, { className: "text-center", padding: "y-3xl", 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: "border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", rounded: "lg", padding: "md", children: _jsxs(Row, { align: "start", justify: "between", gap: "3", wrap: true, children: [_jsxs(Row, { align: "start", className: "flex-1 min-w-0", gap: "3", 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: "truncate", weight: "medium", children: review.productTitle }), _jsxs(Row, { className: "mt-1", align: "center", gap: "sm", wrap: true, children: [_jsx(Stars, { rating: review.rating }), _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", 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", weight: "medium", children: review.title }), _jsx(Text, { className: "mt-1 text-[var(--appkit-color-text-secondary)] line-clamp-3", size: "sm", children: review.comment }), review.sellerReply && (_jsxs(Div, { className: "mt-2 pl-[0.75rem] border-l-2 border-[var(--appkit-color-primary)]", children: [_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: "Store reply:" }), _jsx(Text, { size: "sm", children: review.sellerReply })] }))] })] }), _jsxs(Row, { className: "flex-shrink-0", gap: "sm", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => openReply(review), children: review.sellerReply ? "Edit Reply" : ACTIONS.STORE["reply-review"].label }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setContestTarget(review), children: ACTIONS.STORE["contest-review"].label }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setFeedbackTarget(review), children: ACTIONS.STORE["buyer-feedback"].label })] })] }) }, review.id))) })), meta && meta.totalPages > 1 && (_jsxs(Row, { align: "center", justify: "center", gap: "sm", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => setPage((p) => Math.max(1, p - 1)), disabled: page <= 1 || loading, children: "Previous" }), _jsxs(Text, { size: "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"),
202
+ ] })), loading ? (_jsx(Div, { className: "text-center", padding: "y-xl", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Loading reviews\u2026" }) })) : visibleReviews.length === 0 ? (_jsx(Div, { className: "text-center", padding: "y-3xl", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "No reviews found." }) })) : (_jsx(Stack, { gap: "md", children: visibleReviews.map((review) => (_jsx(Div, { className: "border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", rounded: "lg", padding: "md", children: _jsxs(Row, { align: "start", justify: "between", gap: "3", wrap: true, children: [_jsxs(Row, { align: "start", className: "flex-1 min-w-0", gap: "3", 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: "truncate", weight: "medium", children: review.productTitle }), _jsxs(Row, { className: "mt-1", align: "center", gap: "sm", wrap: true, children: [_jsx(Stars, { rating: review.rating }), _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", 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", weight: "medium", children: review.title }), _jsx(Text, { className: "mt-1 text-[var(--appkit-color-text-secondary)] line-clamp-3", size: "sm", children: review.comment }), review.sellerReply && (_jsxs(Div, { className: "mt-2 pl-[0.75rem] border-l-2 border-[var(--appkit-color-primary)]", children: [_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: "Store reply:" }), _jsx(Text, { size: "sm", children: review.sellerReply })] }))] })] }), _jsxs(Row, { className: "flex-shrink-0", gap: "sm", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => openReply(review), children: review.sellerReply ? "Edit Reply" : ACTIONS.STORE["reply-review"].label }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setContestTarget(review), children: ACTIONS.STORE["contest-review"].label }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setFeedbackTarget(review), children: ACTIONS.STORE["buyer-feedback"].label })] })] }) }, review.id))) })), meta && meta.totalPages > 1 && (_jsxs(Row, { align: "center", justify: "center", gap: "sm", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => setPage((p) => Math.max(1, p - 1)), disabled: page <= 1 || loading, children: "Previous" }), _jsxs(Text, { size: "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"),
182
203
  ] }), _jsx(SideDrawer, { isOpen: !!replyTarget, onClose: () => setReplyTarget(null), title: replyTarget?.sellerReply ? "Edit Reply" : "Reply to Review", mode: "create", children: _jsxs(Stack, { gap: "md", className: `${__P.p4}`, children: [replyTarget && (_jsxs(Div, { className: `${__P.p3} bg-[var(--appkit-color-surface-muted)]`, rounded: "default", children: [_jsx(Stars, { rating: replyTarget.rating }), _jsx(Text, { className: "mt-1", size: "sm", children: replyTarget.comment })] })), replyError && _jsx(Alert, { variant: "error", children: replyError }), _jsxs(Div, { children: [_jsx(Text, { className: "mb-1.5", size: "sm", weight: "medium", children: "Store reply" }), _jsx(Textarea, { rows: 5, value: replyText, onChange: (e) => setReplyText(e.target.value), maxLength: 1000, placeholder: "Write your response to this review\u2026" }), _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)] mt-1", size: "xs", align: "end", children: [replyText.length, "/1000"] })] }), _jsxs(Row, { justify: "end", gap: "sm", 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, { gap: "sm", 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", padding: "2xs", children: [_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", 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, { gap: "sm", 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", padding: "2xs", children: [_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", 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, { gap: "sm", 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", padding: "2xs", children: [_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", 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" })] }) })] }));
183
204
  }
@@ -112,8 +112,6 @@ export { SellerAnalyticsAlertsView as StoreAnalyticsAlertsView } from "./SellerA
112
112
  export type { SellerAnalyticsAlertsViewProps as StoreAnalyticsAlertsViewProps } from "./SellerAnalyticsAlertsView";
113
113
  export { SellerStoreCategoriesView } from "./SellerStoreCategoriesView";
114
114
  export type { SellerStoreCategoriesViewProps } from "./SellerStoreCategoriesView";
115
- export { SellerTemplatesView } from "./SellerTemplatesView";
116
- export type { SellerTemplatesViewProps } from "./SellerTemplatesView";
117
115
  export { SellerPayoutMethodsView } from "./SellerPayoutMethodsView";
118
116
  export type { SellerPayoutMethodsViewProps } from "./SellerPayoutMethodsView";
119
117
  export { SellerShippingConfigsView } from "./SellerShippingConfigsView";
@@ -59,7 +59,6 @@ export { SellerGroupedListingsView as StoreGroupedListingsView } from "./SellerG
59
59
  export { SellerAnalyticsAlertsView } from "./SellerAnalyticsAlertsView";
60
60
  export { SellerAnalyticsAlertsView as StoreAnalyticsAlertsView } from "./SellerAnalyticsAlertsView";
61
61
  export { SellerStoreCategoriesView } from "./SellerStoreCategoriesView";
62
- export { SellerTemplatesView } from "./SellerTemplatesView";
63
62
  export { SellerPayoutMethodsView } from "./SellerPayoutMethodsView";
64
63
  export { SellerShippingConfigsView } from "./SellerShippingConfigsView";
65
64
  export { SellerGoogleReviewsView } from "./SellerGoogleReviewsView";
@@ -65,7 +65,8 @@ const STANDARD_STEPS = [
65
65
  {
66
66
  step: "Pricing",
67
67
  items: [
68
- "Price enter in Indian Rupees (₹). Stored in paise internally.",
68
+ // audit-money-units-ok: instructs sellers there is NO paise conversion, not a stale reference
69
+ "Price — enter in Indian Rupees (₹). Stored as decimal rupees — no paise conversion.",
69
70
  "isOnSale + salePrice — enable the sale badge and show a strikethrough price on the listing.",
70
71
  "customFields — add any extra key/value pairs buyers find useful (e.g. PSA cert number, card number, print run).",
71
72
  ],
@@ -13,5 +13,5 @@ export function StoreProductsView({ storeSlug, labels = {}, renderProducts, rend
13
13
  { value: sortBy("createdAt", "ASC"), label: "Oldest" },
14
14
  { value: sortBy("price", "ASC"), label: "Price: Low to High" },
15
15
  { value: sortBy("price", "DESC"), label: "Price: High to Low" },
16
- ], className: "min-w-44" })), renderFilters: renderFilters, renderActiveFilters: renderActiveFilters, renderBulkActions: () => renderViewToggle?.(viewMode, setViewMode) ?? null, renderTable: () => renderProducts(items, isLoading), renderPagination: () => renderPagination?.(total) ?? null, total: total, isLoading: isLoading }) }));
16
+ ], wrapperClassName: "min-w-44" })), renderFilters: renderFilters, renderActiveFilters: renderActiveFilters, renderBulkActions: () => renderViewToggle?.(viewMode, setViewMode) ?? null, renderTable: () => renderProducts(items, isLoading), renderPagination: () => renderPagination?.(total) ?? null, total: total, isLoading: isLoading }) }));
17
17
  }