@mohasinac/appkit 4.1.0 → 4.1.3

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 (205) 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/maintenance/cloud-logs-data.d.ts +50 -0
  9. package/dist/_internal/server/features/maintenance/cloud-logs-data.js +152 -0
  10. package/dist/_internal/server/jobs/core/onOrderCreate.d.ts +4 -12
  11. package/dist/_internal/server/jobs/core/onOrderCreate.js +3 -3
  12. package/dist/_internal/server/jobs/core/onProductWrite.js +14 -2
  13. package/dist/_internal/shared/features/auctions/config.d.ts +1 -1
  14. package/dist/_internal/shared/features/auctions/config.js +1 -1
  15. package/dist/_internal/shared/features/auctions/schema.js +1 -1
  16. package/dist/_internal/shared/features/cart/schema.js +2 -2
  17. package/dist/_internal/shared/features/events/schema.js +1 -1
  18. package/dist/_internal/shared/features/orders/schema.js +1 -1
  19. package/dist/_internal/shared/features/products/schema.js +3 -3
  20. package/dist/_internal/shared/features/promotions/schema.js +3 -3
  21. package/dist/_internal/shared/fees/calculator.d.ts +6 -7
  22. package/dist/client.d.ts +4 -4
  23. package/dist/client.js +4 -2
  24. package/dist/constants/api-endpoints.d.ts +9 -6
  25. package/dist/constants/api-endpoints.js +3 -2
  26. package/dist/contracts/client-payment-gateway.d.ts +1 -2
  27. package/dist/features/about/components/AboutView.d.ts +6 -16
  28. package/dist/features/about/components/AboutView.js +3 -3
  29. package/dist/features/about/index.d.ts +2 -1
  30. package/dist/features/about/schemas/firestore.d.ts +47 -0
  31. package/dist/features/about/schemas/firestore.js +6 -0
  32. package/dist/features/about/schemas/index.d.ts +210 -0
  33. package/dist/features/about/schemas/index.js +45 -0
  34. package/dist/features/account/hooks/useProfile.js +3 -0
  35. package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +6 -0
  36. package/dist/features/admin/components/AdminCarouselGroupEditorView.js +50 -0
  37. package/dist/features/admin/components/AdminCartsView.js +16 -3
  38. package/dist/features/admin/components/AdminCouponEditorView.js +2 -2
  39. package/dist/features/admin/components/AdminCouponsView.js +14 -4
  40. package/dist/features/admin/components/AdminEmployeeEditorView.js +1 -42
  41. package/dist/features/admin/components/AdminMediaView.js +1 -1
  42. package/dist/features/admin/components/AdminNotificationsView.js +31 -19
  43. package/dist/features/admin/components/AdminOrdersView.js +7 -0
  44. package/dist/features/admin/components/AdminPayoutsView.js +18 -3
  45. package/dist/features/admin/components/AdminProductsView.js +17 -2
  46. package/dist/features/admin/components/AdminReviewsView.js +16 -2
  47. package/dist/features/admin/components/AdminScammersView.js +7 -0
  48. package/dist/features/admin/components/AdminSectionsView.js +4 -0
  49. package/dist/features/admin/components/AdminSiteConfigGuideView.js +1 -1
  50. package/dist/features/admin/components/AdminSiteSettingsView.js +44 -2
  51. package/dist/features/admin/components/AdminStoreEditorView.d.ts +2 -1
  52. package/dist/features/admin/components/AdminStoreEditorView.js +4 -4
  53. package/dist/features/admin/components/AdminStoresView.js +1 -1
  54. package/dist/features/admin/components/AdminSupportTicketDetailView.js +23 -5
  55. package/dist/features/admin/components/AdminSupportTicketsView.js +20 -2
  56. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +4 -1
  57. package/dist/features/admin/components/AdminTesterChecklistView.js +8 -0
  58. package/dist/features/admin/components/index.d.ts +2 -0
  59. package/dist/features/admin/components/index.js +1 -0
  60. package/dist/features/admin/constants/filter-tabs.d.ts +55 -41
  61. package/dist/features/admin/constants/filter-tabs.js +55 -27
  62. package/dist/features/admin/schemas/firestore.d.ts +5 -0
  63. package/dist/features/auctions/actions/bid-actions.js +1 -1
  64. package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
  65. package/dist/features/auctions/components/AuctionsListView.js +8 -1
  66. package/dist/features/auth/components/LoginForm.js +2 -2
  67. package/dist/features/auth/components/RegisterForm.js +2 -2
  68. package/dist/features/auth/permissions/constants.d.ts +16 -0
  69. package/dist/features/auth/permissions/constants.js +53 -0
  70. package/dist/features/auth/schemas/firestore.js +1 -0
  71. package/dist/features/blog/components/BlogFilters.js +1 -1
  72. package/dist/features/cart/schemas/firestore.d.ts +1 -1
  73. package/dist/features/categories/schemas/firestore.d.ts +1 -1
  74. package/dist/features/consultation/components/ConsultationForm.js +1 -1
  75. package/dist/features/events/components/EventFilters.js +2 -0
  76. package/dist/features/homepage/components/WelcomeSection.js +2 -2
  77. package/dist/features/layout/TitleBarLayout.d.ts +2 -2
  78. package/dist/features/layout/TitleBarLayout.js +4 -4
  79. package/dist/features/media/AvatarUpload.js +7 -3
  80. package/dist/features/media/MediaVideo.js +1 -1
  81. package/dist/features/media/finalize.d.ts +9 -13
  82. package/dist/features/media/finalize.js +23 -74
  83. package/dist/features/media/server.d.ts +1 -1
  84. package/dist/features/media/server.js +1 -1
  85. package/dist/features/orders/hooks/useOrders.js +16 -4
  86. package/dist/features/orders/schemas/firestore.d.ts +5 -5
  87. package/dist/features/payments/schemas/firestore.d.ts +2 -2
  88. package/dist/features/pre-orders/components/PreOrdersListView.js +7 -0
  89. package/dist/features/products/components/ArtStickersListView.js +5 -0
  90. package/dist/features/products/components/ProductDetailPageView.d.ts +7 -1
  91. package/dist/features/products/components/ProductForm.js +14 -1
  92. package/dist/features/products/components/ProductsIndexPageView.js +7 -0
  93. package/dist/features/products/constants/action-defs.d.ts +1 -0
  94. package/dist/features/products/constants/action-defs.js +1 -0
  95. package/dist/features/products/schemas/firestore.d.ts +1 -1
  96. package/dist/features/products/schemas/product-templates.d.ts +1 -1
  97. package/dist/features/products/types/index.d.ts +4 -0
  98. package/dist/features/scams/schemas/index.js +2 -2
  99. package/dist/features/search/components/Search.js +3 -3
  100. package/dist/features/seller/components/SellerAuctionsView.js +27 -2
  101. package/dist/features/seller/components/SellerOrdersView.js +1 -1
  102. package/dist/features/seller/components/SellerPayoutsView.js +24 -10
  103. package/dist/features/seller/components/SellerPreOrdersView.js +12 -4
  104. package/dist/features/seller/components/SellerPrizeDrawsView.js +23 -5
  105. package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
  106. package/dist/features/seller/components/SellerProductShell.js +1 -1
  107. package/dist/features/seller/components/SellerProductsView.js +8 -6
  108. package/dist/features/seller/components/SellerReviewsView.js +27 -6
  109. package/dist/features/seller/components/index.d.ts +0 -2
  110. package/dist/features/seller/components/index.js +0 -1
  111. package/dist/features/stores/components/StoreListingsGuideView.js +2 -1
  112. package/dist/features/stores/components/StoreProductsView.js +1 -1
  113. package/dist/features/stores/schemas/firestore.d.ts +11 -0
  114. package/dist/features/stores/schemas/firestore.js +5 -0
  115. package/dist/features/stores/schemas/index.d.ts +2 -2
  116. package/dist/features/tester/actions/checklist-item-actions.d.ts +6 -0
  117. package/dist/features/tester/actions/checklist-item-actions.js +2 -0
  118. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +13 -2
  119. package/dist/features/tester/components/AdminTesterFeedbackListView.js +2 -1
  120. package/dist/features/tester/components/AdminTesterFeedbackReportView.js +9 -5
  121. package/dist/features/tester/components/TesterHubView.js +34 -21
  122. package/dist/features/tester/repository/tester-checklist-item.repository.js +1 -0
  123. package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +18 -4
  124. package/dist/features/tester/repository/tester-checklist-response.repository.js +43 -8
  125. package/dist/features/tester/schemas/firestore.d.ts +8 -3
  126. package/dist/features/tester/schemas/firestore.js +6 -0
  127. package/dist/features/tester/seed-data/bids-tester-seed-data.d.ts +2 -0
  128. package/dist/features/tester/seed-data/bids-tester-seed-data.js +37 -0
  129. package/dist/features/tester/seed-data/coupons-tester-seed-data.d.ts +2 -0
  130. package/dist/features/tester/seed-data/coupons-tester-seed-data.js +76 -0
  131. package/dist/features/tester/seed-data/events-tester-seed-data.js +50 -2
  132. package/dist/features/tester/seed-data/index.d.ts +3 -0
  133. package/dist/features/tester/seed-data/index.js +3 -0
  134. package/dist/features/tester/seed-data/orders-tester-seed-data.d.ts +2 -0
  135. package/dist/features/tester/seed-data/orders-tester-seed-data.js +265 -0
  136. package/dist/features/tester/seed-data/products-tester-seed-data.js +213 -8
  137. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +294 -12
  138. package/dist/features/tester/utils/phases.d.ts +31 -0
  139. package/dist/features/tester/utils/phases.js +46 -0
  140. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +1 -1
  141. package/dist/features/whatsapp-bot/types/index.d.ts +1 -1
  142. package/dist/features/wishlist/types/index.d.ts +3 -0
  143. package/dist/index.d.ts +11 -4
  144. package/dist/index.js +15 -7
  145. package/dist/next/api/routeHandler.js +6 -10
  146. package/dist/next/components/NotFoundView.js +2 -1
  147. package/dist/next/routing/route-map.d.ts +4 -12
  148. package/dist/next/routing/route-map.js +2 -4
  149. package/dist/providers/db-firebase/admin.d.ts +1 -0
  150. package/dist/providers/db-firebase/admin.js +1 -1
  151. package/dist/schemas/registry.d.ts +2 -2
  152. package/dist/seed/addresses-seed-data.js +18 -18
  153. package/dist/seed/bids-seed-data.js +4 -4
  154. package/dist/seed/blog-posts-seed-data.js +82 -82
  155. package/dist/seed/carousel-slides-seed-data.js +14 -10
  156. package/dist/seed/carousels-seed-data.js +3 -3
  157. package/dist/seed/cart-seed-data.js +92 -92
  158. package/dist/seed/claimed-coupons-seed-data.js +10 -10
  159. package/dist/seed/conversations-seed-data.js +75 -75
  160. package/dist/seed/coupon-usage-seed-data.js +12 -12
  161. package/dist/seed/coupons-seed-data.js +41 -41
  162. package/dist/seed/events-seed-data.js +105 -105
  163. package/dist/seed/grouped-listings-seed-data.d.ts +2 -137
  164. package/dist/seed/grouped-listings-seed-data.js +79 -143
  165. package/dist/seed/history-seed-data.js +38 -38
  166. package/dist/seed/homepage-sections-seed-data.js +9 -9
  167. package/dist/seed/index.d.ts +1 -1
  168. package/dist/seed/index.js +1 -1
  169. package/dist/seed/manifest.js +4 -4
  170. package/dist/seed/notifications-seed-data.js +56 -56
  171. package/dist/seed/offers-seed-data.js +91 -91
  172. package/dist/seed/orders-seed-data.js +125 -106
  173. package/dist/seed/payouts-seed-data.js +42 -42
  174. package/dist/seed/products-auctions-seed-data.js +10 -2
  175. package/dist/seed/products-classifieds-seed-data.js +48 -5
  176. package/dist/seed/products-digital-codes-seed-data.js +48 -5
  177. package/dist/seed/products-live-items-seed-data.js +6 -1
  178. package/dist/seed/products-prize-draws-seed-data.js +58 -5
  179. package/dist/seed/products-standard-seed-data.js +40 -8
  180. package/dist/seed/reviews-seed-data.js +42 -23
  181. package/dist/seed/scammers-seed-data.js +22 -22
  182. package/dist/seed/sessions-seed-data.js +7 -7
  183. package/dist/seed/shipments-seed-data.js +10 -10
  184. package/dist/seed/site-settings-seed-data.js +82 -0
  185. package/dist/seed/store-addresses-seed-data.js +2 -2
  186. package/dist/seed/store-extensions-seed-data.js +93 -93
  187. package/dist/seed/stores-seed-data.js +2 -0
  188. package/dist/seed/support-tickets-seed-data.js +47 -47
  189. package/dist/seed/users-seed-data.js +55 -89
  190. package/dist/seed/wishlists-seed-data.js +22 -22
  191. package/dist/server.d.ts +2 -1
  192. package/dist/server.js +5 -5
  193. package/dist/styles.css +1 -1
  194. package/dist/tailwind-utilities.css +1 -1
  195. package/dist/ui/components/PageLoader.js +8 -3
  196. package/dist/ui/components/Select.d.ts +8 -1
  197. package/dist/ui/components/Select.js +2 -2
  198. package/dist/ui/components/SiteMark.d.ts +23 -0
  199. package/dist/ui/components/SiteMark.js +76 -0
  200. package/dist/ui/index.d.ts +2 -0
  201. package/dist/ui/index.js +1 -0
  202. package/dist/validation/schemas.d.ts +0 -2
  203. package/dist/validation/schemas.js +0 -2
  204. package/package.json +1 -1
  205. package/scripts/seed-cli.mjs +27 -19
@@ -19,9 +19,9 @@ export type PayoutStatus = "pending" | "processing" | "paid" | "failed";
19
19
  export interface PayoutRefundDeduction {
20
20
  orderId: string;
21
21
  refundId: string;
22
- /** Gross refund amount in paise (for audit trail). */
22
+ /** Gross refund amount in decimal rupees (for audit trail). */
23
23
  refundedAmount: number;
24
- /** Net amount deducted from the seller payout in paise. */
24
+ /** Net amount deducted from the seller payout in decimal rupees. */
25
25
  deductedAmount: number;
26
26
  reason: string;
27
27
  appliedAt: Date;
@@ -26,6 +26,13 @@ function buildPreOrderFilters(params) {
26
26
  const preOrderProductionStatus = sp(params, "preOrderProductionStatus");
27
27
  if (preOrderProductionStatus)
28
28
  parts.push(sieveFilter("preOrderProductionStatus", SIEVE_OP.EQ, preOrderProductionStatus));
29
+ // Mirror PreOrdersIndexListing's client-side default (same Root Cause pattern as
30
+ // auctions' dateFrom default — SSR initialData is seeded into React Query with
31
+ // staleTime:Infinity, so if this filter doesn't already exclude closed/out-of-stock
32
+ // pre-orders, the client never refetches and they leak into the default view).
33
+ const showClosed = sp(params, "showClosed") === "true";
34
+ if (!showClosed)
35
+ parts.push(sieveFilter("stockQuantity", SIEVE_OP.GT, 0));
29
36
  return parts.join(",");
30
37
  }
31
38
  export async function PreOrdersListView({ searchParams = {} }) {
@@ -42,6 +42,11 @@ function buildArtStickersFilters(params) {
42
42
  const storeId = sp(params, "seller");
43
43
  if (storeId)
44
44
  parts.push(sieveFilter(PRODUCT_FIELDS.STORE_ID, SIEVE_OP.EQ, storeId));
45
+ // Mirror ProductsIndexListing's client-side "Show sold" default — see
46
+ // ProductsIndexPageView.tsx's buildProductFilters for the full explanation.
47
+ const showSold = sp(params, "showSold") === "true";
48
+ if (!showSold)
49
+ parts.push(sieveFilter(PRODUCT_FIELDS.STOCK_QUANTITY, SIEVE_OP.GT, 0));
45
50
  return sieveAnd(...parts);
46
51
  }
47
52
  export async function ArtStickersListView({ searchParams = {} }) {
@@ -44,7 +44,13 @@ export interface ProductDetailPageViewProps {
44
44
  productFeatures?: ProductFeatureDocument[];
45
45
  /** Site-wide COD availability (`siteSettings.payment.codEnabled`), threaded from the page's data layer. Drives the "Cash on Delivery" feature badge. */
46
46
  codEnabled?: boolean;
47
- /** Site-wide EMI availability (`siteSettings.emi.enabled`), threaded from the page's data layer. Drives the "EMI Available" feature badge. */
47
+ /**
48
+ * Fully-resolved EMI eligibility for this product — site-wide flag AND the
49
+ * seller's `StoreDocument.emiEnabled` opt-in AND price above
50
+ * `siteSettings.emi.minOrderValue`, computed by the page's data layer
51
+ * (mirrors `checkEmiEligibility()`'s checkout-time rule). Drives the "EMI
52
+ * Available" feature badge.
53
+ */
48
54
  emiEnabled?: boolean;
49
55
  }
50
56
  export declare function ProductDetailPageView({ slug, initialProduct, renderOfferAction, renderPrimaryActions, productFeatures, codEnabled, emiEnabled, }: ProductDetailPageViewProps): Promise<import("react").JSX.Element>;
@@ -132,7 +132,20 @@ export function ProductForm({ product, onChange, isReadonly = false, renderDescr
132
132
  thumbnailUrl: media.thumbnailUrl,
133
133
  },
134
134
  });
135
- }, onUpload: handleVideoUpload, accept: "video/*", maxSizeMB: 50, helperText: t("formVideoHelper"), onAbort: onMediaAbort })), _jsx(TagInput, { label: t("formTags"), value: product.tags || [], onChange: (tags) => update({ tags }), disabled: isReadonly, placeholder: t("formTagsPlaceholder") }), _jsxs(FormGroup, { columns: 2, children: [_jsx(Checkbox, { label: t("formFeatured"), checked: !!product.featured, onChange: (e) => update({ featured: e.target.checked }), disabled: isReadonly }), _jsx(Checkbox, { label: t("formIsPromoted"), checked: !!product.isPromoted, onChange: (e) => update({ isPromoted: e.target.checked }), disabled: isReadonly }), _jsx(Checkbox, { label: t("formAllowShipBeforeEmiComplete"), checked: !!product.allowShipBeforeEmiComplete, onChange: (e) => update({ allowShipBeforeEmiComplete: e.target.checked }), disabled: isReadonly })] }), _jsx(Heading, { level: 4, className: "mt-4", children: t("sectionConditionShipping") }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "condition", label: t("formCondition"), type: "select", value: product.condition || "new", onChange: (value) => update({
135
+ }, onUpload: handleVideoUpload, accept: "video/*", maxSizeMB: 50, helperText: t("formVideoHelper"), onAbort: onMediaAbort })), _jsx(TagInput, { label: t("formTags"), value: product.tags || [], onChange: (tags) => update({ tags }), disabled: isReadonly, placeholder: t("formTagsPlaceholder") }), _jsxs(FormGroup, { columns: 2, children: [_jsx(Checkbox, { label: t("formFeatured"), checked: !!product.featured, onChange: (e) => update({ featured: e.target.checked }), disabled: isReadonly }), _jsx(Checkbox, { label: t("formIsPromoted"), checked: !!product.isPromoted, onChange: (e) => update({ isPromoted: e.target.checked }), disabled: isReadonly }), _jsx(Checkbox, { label: t("formAllowShipBeforeEmiComplete"), checked: !!product.allowShipBeforeEmiComplete, onChange: (e) => update({ allowShipBeforeEmiComplete: e.target.checked }), disabled: isReadonly }), _jsx(Checkbox, { label: t("formAllowOffers"), checked: !!product.allowOffers, onChange: (e) => update({
136
+ allowOffers: e.target.checked,
137
+ minOfferPercent: e.target.checked ? product.minOfferPercent ?? 50 : undefined,
138
+ }), disabled: isReadonly })] }), product.allowOffers && (_jsxs(_Fragment, { children: [_jsx(Alert, { variant: "info", title: t("formAllowOffersHelp"), children: t("formAllowOffersHelp") }), _jsx(FormField, { name: "minOfferPercent", label: t("formMinOfferPercent"), type: "number", value: String(product.minOfferPercent ?? ""), onChange: (value) => update({ minOfferPercent: Number(value) }), disabled: isReadonly, placeholder: "50" })] })), _jsx(Heading, { level: 4, className: "mt-4", children: t("sectionTaxGst") }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "gstRate", label: t("formGstRate"), type: "select",
139
+ // Unset is semantically "exempt" (see ProductDocument.gstRate JSDoc),
140
+ // same as 0% — default the control to 0 so the select stays a fixed
141
+ // 5-option enum instead of adding a 6th "unset" placeholder entry.
142
+ value: String(product.gstRate ?? 0), onChange: (value) => update({ gstRate: Number(value) }), disabled: isReadonly, options: [
143
+ { value: "0", label: "0%" },
144
+ { value: "5", label: "5%" },
145
+ { value: "12", label: "12%" },
146
+ { value: "18", label: "18%" },
147
+ { value: "28", label: "28%" },
148
+ ] }), _jsx(FormField, { name: "hsnCode", label: t("formHsnCode"), type: "text", value: product.hsnCode ?? "", onChange: (value) => update({ hsnCode: value }), disabled: isReadonly, placeholder: "e.g. 9503" })] }), _jsx(Heading, { level: 4, className: "mt-4", children: t("sectionConditionShipping") }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "condition", label: t("formCondition"), type: "select", value: product.condition || "new", onChange: (value) => update({
136
149
  condition: value,
137
150
  }), disabled: isReadonly, options: [
138
151
  { value: "new", label: t("formConditionNew") },
@@ -47,6 +47,13 @@ function buildProductFilters(params) {
47
47
  if (freeShipping === "true") {
48
48
  parts.push(sieveFilter(PRODUCT_FIELDS.SHIPPING_PAID_BY, SIEVE_OP.EQ, PRODUCT_FIELDS.SHIPPING_PAID_BY_VALUES.SELLER));
49
49
  }
50
+ // Mirror ProductsIndexListing's client-side "Show sold" default (same Root Cause
51
+ // pattern as auctions' dateFrom default — SSR initialData is seeded into React
52
+ // Query with staleTime:Infinity, so if this filter doesn't already exclude sold /
53
+ // out-of-stock products, the client never refetches and they leak into the default view).
54
+ const showSold = sp(params, "showSold") === "true";
55
+ if (!showSold)
56
+ parts.push(sieveFilter(PRODUCT_FIELDS.STOCK_QUANTITY, SIEVE_OP.GT, 0));
50
57
  return sieveAnd(...parts);
51
58
  }
52
59
  export async function ProductsIndexPageView({ searchParams = {} }) {
@@ -350,6 +350,7 @@ export declare const ADMIN_BULK_ACTIONS: {
350
350
  };
351
351
  export declare const SELLER_BULK_ACTIONS: {
352
352
  readonly products: readonly ["row-publish", "row-archive", "row-delete"];
353
+ readonly payouts: readonly ["row-export"];
353
354
  readonly coupons: readonly ["row-archive", "row-delete"];
354
355
  readonly bids: readonly ["row-cancel"];
355
356
  readonly bundles: readonly ["row-delete"];
@@ -451,6 +451,7 @@ export const ADMIN_BULK_ACTIONS = {
451
451
  // Seller bulk actions per listing entity
452
452
  export const SELLER_BULK_ACTIONS = {
453
453
  products: [ROW_ACTION_ID.PUBLISH, ROW_ACTION_ID.ARCHIVE, ROW_ACTION_ID.DELETE],
454
+ payouts: [ROW_ACTION_ID.EXPORT],
454
455
  coupons: [ROW_ACTION_ID.ARCHIVE, ROW_ACTION_ID.DELETE],
455
456
  bids: [ROW_ACTION_ID.CANCEL],
456
457
  bundles: [ROW_ACTION_ID.DELETE],
@@ -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) => ({
@@ -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) => {