@mohasinac/appkit 4.11.2 → 4.12.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 (246) hide show
  1. package/dist/_internal/server/features/checkout/actions.d.ts +40 -3
  2. package/dist/_internal/server/features/checkout/actions.js +232 -47
  3. package/dist/_internal/server/features/checkout/data.d.ts +10 -0
  4. package/dist/_internal/server/features/orders/adapters.js +20 -0
  5. package/dist/_internal/server/features/products/data.d.ts +12 -3
  6. package/dist/_internal/server/features/products/data.js +55 -4
  7. package/dist/_internal/server/features/products/index.d.ts +1 -1
  8. package/dist/_internal/server/features/products/index.js +1 -1
  9. package/dist/_internal/server/features/products/list-public.d.ts +47 -0
  10. package/dist/_internal/server/features/products/list-public.js +131 -6
  11. package/dist/_internal/server/features/reviews/data.d.ts +1 -1
  12. package/dist/_internal/server/features/reviews/data.js +3 -2
  13. package/dist/_internal/server/features/tester/visibility.js +3 -3
  14. package/dist/_internal/server/jobs/core/auctionSettlement.js +5 -2
  15. package/dist/_internal/server/jobs/core/testerSandboxCleanup.d.ts +1 -1
  16. package/dist/_internal/server/jobs/core/testerSandboxCleanup.js +4 -1
  17. package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +2 -0
  18. package/dist/_internal/server/jobs/handlers/_helpers.js +4 -1
  19. package/dist/_internal/shared/checkout/lanes.d.ts +20 -0
  20. package/dist/_internal/shared/checkout/lanes.js +28 -0
  21. package/dist/_internal/shared/features/checkout/config.d.ts +4 -0
  22. package/dist/_internal/shared/features/checkout/config.js +8 -0
  23. package/dist/_internal/shared/features/promotions/schema.d.ts +0 -16
  24. package/dist/_internal/shared/features/promotions/schema.js +0 -2
  25. package/dist/_internal/shared/features/reviews/config.d.ts +8 -1
  26. package/dist/_internal/shared/features/reviews/config.js +8 -1
  27. package/dist/_internal/shared/fees/calculator.d.ts +34 -2
  28. package/dist/_internal/shared/fees/calculator.js +45 -1
  29. package/dist/_internal/shared/listing-types/_registry.d.ts +88 -1
  30. package/dist/_internal/shared/listing-types/_registry.js +63 -1
  31. package/dist/_internal/shared/listing-types/art/config.d.ts +59 -0
  32. package/dist/_internal/shared/listing-types/art/config.js +12 -0
  33. package/dist/_internal/shared/listing-types/auction/config.d.ts +53 -0
  34. package/dist/_internal/shared/listing-types/auction/config.js +17 -0
  35. package/dist/_internal/shared/listing-types/classified/config.d.ts +59 -0
  36. package/dist/_internal/shared/listing-types/classified/config.js +14 -0
  37. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +59 -0
  38. package/dist/_internal/shared/listing-types/digital-code/config.js +12 -0
  39. package/dist/_internal/shared/listing-types/live/config.d.ts +59 -0
  40. package/dist/_internal/shared/listing-types/live/config.js +14 -0
  41. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +110 -0
  42. package/dist/_internal/shared/listing-types/pre-order/config.js +14 -0
  43. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +44 -0
  44. package/dist/_internal/shared/listing-types/prize-draw/config.js +10 -0
  45. package/dist/_internal/shared/listing-types/standard/config.d.ts +59 -0
  46. package/dist/_internal/shared/listing-types/standard/config.js +9 -0
  47. package/dist/_internal/shared/listing-types/stickers/config.d.ts +59 -0
  48. package/dist/_internal/shared/listing-types/stickers/config.js +12 -0
  49. package/dist/client.d.ts +12 -2
  50. package/dist/client.js +8 -2
  51. package/dist/constants/api-endpoints.d.ts +6 -0
  52. package/dist/constants/api-endpoints.js +2 -0
  53. package/dist/constants/field-names.d.ts +12 -0
  54. package/dist/constants/field-names.js +12 -0
  55. package/dist/constants/table-keys.d.ts +2 -0
  56. package/dist/constants/table-keys.js +2 -0
  57. package/dist/features/account/components/UserOrdersView.js +27 -5
  58. package/dist/features/admin/components/AdminAddressesView.js +21 -11
  59. package/dist/features/admin/components/AdminArtView.d.ts +9 -0
  60. package/dist/features/admin/components/AdminArtView.js +11 -35
  61. package/dist/features/admin/components/AdminClassifiedView.d.ts +9 -0
  62. package/dist/features/admin/components/AdminClassifiedView.js +11 -35
  63. package/dist/features/admin/components/AdminCouponEditorView.js +1 -4
  64. package/dist/features/admin/components/AdminDigitalCodesView.d.ts +9 -0
  65. package/dist/features/admin/components/AdminDigitalCodesView.js +11 -42
  66. package/dist/features/admin/components/AdminLiveView.d.ts +9 -0
  67. package/dist/features/admin/components/AdminLiveView.js +11 -41
  68. package/dist/features/admin/components/AdminOrderEditorView.d.ts +8 -1
  69. package/dist/features/admin/components/AdminOrderEditorView.js +4 -3
  70. package/dist/features/admin/components/AdminOrdersView.js +14 -1
  71. package/dist/features/admin/components/AdminPaymentMethodsView.js +18 -11
  72. package/dist/features/admin/components/AdminProductsView.js +34 -18
  73. package/dist/features/admin/components/AdminSiteConfigGuideView.js +1 -1
  74. package/dist/features/admin/components/AdminSiteSettingsView.js +4 -2
  75. package/dist/features/admin/components/AdminStickersView.d.ts +9 -0
  76. package/dist/features/admin/components/AdminStickersView.js +11 -35
  77. package/dist/features/admin/constants/filter-tabs.d.ts +26 -9
  78. package/dist/features/admin/constants/filter-tabs.js +22 -9
  79. package/dist/features/admin/hooks/useAdminListing.js +7 -2
  80. package/dist/features/admin/schemas/firestore.d.ts +5 -0
  81. package/dist/features/admin/schemas/firestore.js +1 -0
  82. package/dist/features/auctions/actions/bid-actions.js +2 -2
  83. package/dist/features/auctions/components/AuctionBidsTable.js +1 -1
  84. package/dist/features/auctions/components/AuctionBottomActions.js +1 -0
  85. package/dist/features/auctions/components/AuctionDetailPageView.js +13 -11
  86. package/dist/features/cart/components/CartDrawer.d.ts +9 -1
  87. package/dist/features/cart/components/CartDrawer.js +4 -3
  88. package/dist/features/cart/components/CartPriceBreakdown.d.ts +61 -0
  89. package/dist/features/cart/components/CartPriceBreakdown.js +25 -0
  90. package/dist/features/cart/components/StoreAddonsPicker.d.ts +47 -0
  91. package/dist/features/cart/components/StoreAddonsPicker.js +21 -0
  92. package/dist/features/cart/components/index.d.ts +4 -0
  93. package/dist/features/cart/components/index.js +2 -0
  94. package/dist/features/cart/repository/cart.repository.d.ts +20 -2
  95. package/dist/features/cart/repository/cart.repository.js +49 -0
  96. package/dist/features/cart/schemas/firestore.d.ts +26 -2
  97. package/dist/features/categories/components/BrandDetailTabs.js +31 -11
  98. package/dist/features/categories/components/CategoryDetailTabs.js +27 -7
  99. package/dist/features/categories/components/CategoryProductsListing.d.ts +9 -1
  100. package/dist/features/categories/components/CategoryProductsListing.js +5 -2
  101. package/dist/features/classified/components/ClassifiedDetailPageView.js +2 -1
  102. package/dist/features/contact/email.js +5 -1
  103. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +17 -16
  104. package/dist/features/homepage/components/FeaturedResultsSection.js +1 -1
  105. package/dist/features/homepage/components/SectionCarousel.js +1 -1
  106. package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
  107. package/dist/features/layout/AppLayoutShell.js +9 -2
  108. package/dist/features/layout/BottomActions.js +62 -6
  109. package/dist/features/layout/BottomActionsContext.d.ts +26 -0
  110. package/dist/features/layout/BottomActionsContext.js +18 -1
  111. package/dist/features/layout/hooks/useBottomActions.d.ts +12 -1
  112. package/dist/features/layout/hooks/useBottomActions.js +14 -1
  113. package/dist/features/layout/index.d.ts +1 -1
  114. package/dist/features/live/components/LiveItemDetailPageView.js +7 -6
  115. package/dist/features/orders/components/OrderAddonBadges.d.ts +36 -0
  116. package/dist/features/orders/components/OrderAddonBadges.js +28 -0
  117. package/dist/features/orders/components/index.d.ts +1 -0
  118. package/dist/features/orders/components/index.js +1 -0
  119. package/dist/features/orders/repository/orders.repository.d.ts +36 -0
  120. package/dist/features/orders/repository/orders.repository.js +18 -0
  121. package/dist/features/orders/types/index.d.ts +38 -0
  122. package/dist/features/pre-orders/components/PreOrderBottomActions.d.ts +1 -1
  123. package/dist/features/pre-orders/components/PreOrderBottomActions.js +3 -19
  124. package/dist/features/products/components/ArtStickersListView.js +5 -4
  125. package/dist/features/products/components/AuctionsIndexListing.js +1 -2
  126. package/dist/features/products/components/ListingBottomActions.d.ts +28 -0
  127. package/dist/features/products/components/ListingBottomActions.js +25 -0
  128. package/dist/features/products/components/PrizeDrawBottomActions.d.ts +1 -1
  129. package/dist/features/products/components/PrizeDrawBottomActions.js +3 -21
  130. package/dist/features/products/components/PrizeDrawsListingView.js +18 -33
  131. package/dist/features/products/components/ProductDetailActions.js +4 -0
  132. package/dist/features/products/components/ProductDetailPageView.js +7 -7
  133. package/dist/features/products/components/ProductsIndexListing.js +87 -11
  134. package/dist/features/products/components/ProductsIndexPageView.js +14 -2
  135. package/dist/features/products/config/listing-type-listing-config.d.ts +31 -0
  136. package/dist/features/products/config/listing-type-listing-config.js +74 -0
  137. package/dist/features/products/constants/listing-tabs.d.ts +43 -198
  138. package/dist/features/products/constants/listing-tabs.js +71 -58
  139. package/dist/features/products/constants/sieve.d.ts +104 -1
  140. package/dist/features/products/constants/sieve.js +27 -7
  141. package/dist/features/products/hooks/useProducts.js +14 -0
  142. package/dist/features/products/repository/products.repository.d.ts +45 -1
  143. package/dist/features/products/repository/products.repository.js +26 -3
  144. package/dist/features/products/types/index.d.ts +15 -0
  145. package/dist/features/products/utils/listing-badge-variant.d.ts +20 -1
  146. package/dist/features/products/utils/listing-badge-variant.js +26 -1
  147. package/dist/features/products/utils/listing-type.d.ts +24 -0
  148. package/dist/features/products/utils/listing-type.js +48 -0
  149. package/dist/features/promotions/actions/coupon-actions.d.ts +7 -0
  150. package/dist/features/promotions/actions/coupon-actions.js +12 -1
  151. package/dist/features/promotions/actions/coupon-stacking.d.ts +24 -0
  152. package/dist/features/promotions/actions/coupon-stacking.js +21 -0
  153. package/dist/features/promotions/actions/index.d.ts +1 -0
  154. package/dist/features/promotions/actions/index.js +1 -0
  155. package/dist/features/promotions/actions/seller-coupon-actions.d.ts +0 -1
  156. package/dist/features/promotions/actions/seller-coupon-actions.js +0 -1
  157. package/dist/features/promotions/components/CouponHelpDetails.d.ts +20 -0
  158. package/dist/features/promotions/components/CouponHelpDetails.js +17 -0
  159. package/dist/features/promotions/components/CouponsIndexListing.js +2 -1
  160. package/dist/features/promotions/components/index.d.ts +2 -0
  161. package/dist/features/promotions/components/index.js +1 -0
  162. package/dist/features/promotions/constants/coupon-help.d.ts +27 -0
  163. package/dist/features/promotions/constants/coupon-help.js +42 -0
  164. package/dist/features/promotions/repository/coupons.repository.d.ts +10 -1
  165. package/dist/features/promotions/repository/coupons.repository.js +22 -8
  166. package/dist/features/promotions/schemas/firestore.d.ts +0 -1
  167. package/dist/features/promotions/schemas/firestore.js +1 -1
  168. package/dist/features/promotions/schemas/index.d.ts +0 -20
  169. package/dist/features/promotions/schemas/index.js +0 -1
  170. package/dist/features/reviews/actions/review-actions.js +5 -1
  171. package/dist/features/reviews/components/ReviewDetailPageView.js +2 -1
  172. package/dist/features/reviews/components/ReviewFilters.d.ts +7 -1
  173. package/dist/features/reviews/components/ReviewFilters.js +4 -3
  174. package/dist/features/reviews/components/ReviewsIndexListing.d.ts +6 -0
  175. package/dist/features/reviews/components/ReviewsIndexListing.js +9 -109
  176. package/dist/features/reviews/components/ReviewsListingPanel.d.ts +41 -0
  177. package/dist/features/reviews/components/ReviewsListingPanel.js +226 -0
  178. package/dist/features/reviews/components/index.d.ts +2 -0
  179. package/dist/features/reviews/components/index.js +1 -0
  180. package/dist/features/reviews/hooks/useReviews.d.ts +8 -0
  181. package/dist/features/reviews/hooks/useReviews.js +14 -6
  182. package/dist/features/reviews/repository/reviews.repository.d.ts +29 -1
  183. package/dist/features/reviews/repository/reviews.repository.js +77 -41
  184. package/dist/features/reviews/types/index.d.ts +8 -1
  185. package/dist/features/seller/components/SellerOrdersView.js +2 -1
  186. package/dist/features/seller/components/SellerProductsCards.js +2 -2
  187. package/dist/features/seller/components/SellerProductsView.js +21 -18
  188. package/dist/features/stores/actions/store-query-actions.js +1 -1
  189. package/dist/features/stores/components/StoreArtStickersPageView.d.ts +21 -0
  190. package/dist/features/stores/components/StoreArtStickersPageView.js +30 -0
  191. package/dist/features/stores/components/StoreAuctionsListing.js +5 -11
  192. package/dist/features/stores/components/StoreAuctionsPageView.d.ts +4 -1
  193. package/dist/features/stores/components/StoreAuctionsPageView.js +17 -17
  194. package/dist/features/stores/components/StoreClassifiedsListing.js +7 -0
  195. package/dist/features/stores/components/StoreClassifiedsPageView.d.ts +4 -1
  196. package/dist/features/stores/components/StoreClassifiedsPageView.js +16 -12
  197. package/dist/features/stores/components/StoreDetailLayoutView.js +4 -3
  198. package/dist/features/stores/components/StoreDigitalCodesListing.js +5 -0
  199. package/dist/features/stores/components/StoreDigitalCodesPageView.d.ts +4 -1
  200. package/dist/features/stores/components/StoreDigitalCodesPageView.js +16 -12
  201. package/dist/features/stores/components/StoreLiveItemsListing.js +7 -0
  202. package/dist/features/stores/components/StoreLiveItemsPageView.d.ts +4 -1
  203. package/dist/features/stores/components/StoreLiveItemsPageView.js +16 -12
  204. package/dist/features/stores/components/StorePreOrdersListing.js +6 -11
  205. package/dist/features/stores/components/StorePreOrdersPageView.d.ts +4 -1
  206. package/dist/features/stores/components/StorePreOrdersPageView.js +17 -17
  207. package/dist/features/stores/components/StorePrizeDrawsPageView.d.ts +4 -8
  208. package/dist/features/stores/components/StorePrizeDrawsPageView.js +17 -22
  209. package/dist/features/stores/components/StoreProductsListing.d.ts +7 -1
  210. package/dist/features/stores/components/StoreProductsListing.js +3 -2
  211. package/dist/features/stores/components/StoreProductsPageView.d.ts +1 -1
  212. package/dist/features/stores/components/StoreProductsPageView.js +16 -21
  213. package/dist/features/stores/components/StoreReviewsListing.d.ts +4 -0
  214. package/dist/features/stores/components/StoreReviewsListing.js +7 -108
  215. package/dist/features/stores/schemas/index.d.ts +2 -2
  216. package/dist/features/tester/seed-data/coupons-tester-seed-data.js +3 -3
  217. package/dist/features/tester/seed-data/index.d.ts +1 -0
  218. package/dist/features/tester/seed-data/index.js +1 -0
  219. package/dist/features/tester/seed-data/offers-tester-seed-data.d.ts +2 -0
  220. package/dist/features/tester/seed-data/offers-tester-seed-data.js +61 -0
  221. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +939 -1
  222. package/dist/index.d.ts +9 -3
  223. package/dist/index.js +11 -2
  224. package/dist/next/routing/route-map.d.ts +2 -0
  225. package/dist/next/routing/route-map.js +5 -0
  226. package/dist/react/hooks/useListingTypeFlags.d.ts +12 -19
  227. package/dist/react/hooks/useListingTypeFlags.js +4 -9
  228. package/dist/schemas/registry.d.ts +0 -12
  229. package/dist/seed/claimed-coupons-seed-data.js +3 -3
  230. package/dist/seed/coupons-seed-data.js +29 -10
  231. package/dist/seed/faq-seed-data.js +2 -2
  232. package/dist/seed/manifest.js +2 -2
  233. package/dist/seed/reviews-seed-data.js +52 -0
  234. package/dist/server-entry.d.ts +1 -0
  235. package/dist/server-entry.js +1 -0
  236. package/dist/styles.css +55 -39
  237. package/dist/tailwind-utilities.css +1 -1
  238. package/dist/ui/components/Checkbox.d.ts +10 -1
  239. package/dist/ui/components/Checkbox.js +14 -2
  240. package/dist/ui/components/Checkbox.style.css +11 -0
  241. package/dist/ui/components/FilterChipGroup.d.ts +21 -3
  242. package/dist/ui/components/FilterChipGroup.js +33 -4
  243. package/dist/ui/components/HorizontalScroller.d.ts +1 -2
  244. package/dist/ui/components/HorizontalScroller.js +9 -5
  245. package/dist/ui/components/HorizontalScroller.style.css +43 -38
  246. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { sortBy } from "@mohasinac/appkit/client";
4
- import { useState } from "react";
4
+ import { useUrlTable } from "../../../react/hooks/useUrlTable";
5
5
  import { useQueryClient } from "@tanstack/react-query";
6
6
  import { Badge, Div, FilterChipGroup, Span, Stack, Text, useToast } from "../../../ui";
7
7
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
@@ -44,20 +44,24 @@ const PM_COLUMNS = [
44
44
  render: (row) => _jsx(Span, { size: "sm", color: "muted", children: row.updatedAt }),
45
45
  },
46
46
  ];
47
- function buildConfig(banStatus, onAction) {
47
+ function buildConfig(onAction) {
48
48
  return {
49
49
  portal: "admin",
50
50
  title: "Payment Methods",
51
51
  searchPlaceholder: "Search by type or owner",
52
52
  emptyLabel: "No payment methods found",
53
- filterKeys: [],
53
+ // URL-synced via filterKeys (2026-08-21) — see the matching note in
54
+ // AdminAddressesView. banStatus is a query param, not a Sieve clause,
55
+ // hence buildExtraParams rather than buildFilters.
56
+ filterKeys: ["banStatus"],
54
57
  defaultSort: sortBy("bannedAt", "DESC"),
55
- queryKey: ["admin", "payment-methods", banStatus],
56
- endpoint: banStatus
57
- ? `${ADMIN_ENDPOINTS.PAYMENT_METHODS}?banStatus=${encodeURIComponent(banStatus)}`
58
- : ADMIN_ENDPOINTS.PAYMENT_METHODS,
58
+ queryKey: ["admin", "payment-methods"],
59
+ endpoint: ADMIN_ENDPOINTS.PAYMENT_METHODS,
60
+ buildExtraParams: (state) => state.banStatus ? { banStatus: state.banStatus } : undefined,
59
61
  sortOptions: [
60
- { value: "bannedAt", label: "Flagged Date" },
62
+ // -bannedAt (newest flagged first) to match defaultSort — the option was
63
+ // "bannedAt" (oldest first), so the dropdown opened with nothing selected.
64
+ { value: sortBy("bannedAt", "DESC"), label: "Flagged Date" },
61
65
  { value: "type", label: "Type" },
62
66
  ],
63
67
  columns: PM_COLUMNS,
@@ -107,7 +111,10 @@ function buildConfig(banStatus, onAction) {
107
111
  };
108
112
  }
109
113
  export function AdminPaymentMethodsView(_props) {
110
- const [banStatus, setBanStatus] = useState("");
114
+ // Independent useUrlTable() against the same URL param the listing reads
115
+ // via filterKeys (Root Cause #35 pattern).
116
+ const chipTable = useUrlTable({ defaults: {} });
117
+ const banStatus = chipTable.get("banStatus");
111
118
  const queryClient = useQueryClient();
112
119
  const { showToast } = useToast();
113
120
  const actionMutation = useApiMutation({
@@ -125,6 +132,6 @@ export function AdminPaymentMethodsView(_props) {
125
132
  const handleAction = (id, action) => {
126
133
  actionMutation.mutate({ id, action });
127
134
  };
128
- const config = buildConfig(banStatus, handleAction);
129
- return (_jsxs(Stack, { gap: "md", children: [_jsx(Div, { padding: "inline", border: "default", className: "border-b", children: _jsx(FilterChipGroup, { label: "Status", tabs: BAN_STATUS_TABS, value: banStatus, onChange: (v) => setBanStatus(v) }) }), _jsx(DataListingView, { config: config })] }));
135
+ const config = buildConfig(handleAction);
136
+ return (_jsxs(Stack, { gap: "md", children: [_jsx(Div, { padding: "inline", border: "default", className: "border-b", children: _jsx(FilterChipGroup, { label: "Status", tabs: BAN_STATUS_TABS, value: banStatus, onChange: (v) => chipTable.set("banStatus", v) }) }), _jsx(DataListingView, { config: config })] }));
130
137
  }
@@ -15,21 +15,17 @@ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
15
15
  import { AdminProductEditorView } from "./AdminProductEditorView";
16
16
  import { QuickEditMenu } from "./QuickEditMenu";
17
17
  import { ADMIN_BULK_ACTIONS, ROW_ACTION_META, ROW_ACTION_ID } from "../../products/constants/action-defs";
18
+ import { parseSelectedListingTypes } from "../../products/utils/listing-type";
18
19
  const FLAG_DEFS = [
19
20
  { key: "featured", label: "Featured" },
20
21
  { key: "isPromoted", label: "Promoted" },
21
22
  { key: "isOnSale", label: "On Sale" },
22
23
  { key: "isSold", label: "Sold" },
23
24
  ];
24
- const TYPE_FILTER_MAP = {
25
- Auctions: "listingType==auction",
26
- "Pre-orders": "listingType==pre-order",
27
- "Prize Draws": "listingType==prize-draw",
28
- Products: "listingType==standard",
29
- Classifieds: "listingType==classified",
30
- "Digital Codes": "listingType==digital-code",
31
- "Live Items": "listingType==live",
32
- };
25
+ // TYPE_FILTER_MAP (label -> Sieve clause) was deleted 2026-08-21. Chip ids are
26
+ // now canonical ListingType values, so the clause is built directly and the
27
+ // array is covered by audit-filter-tab-enums. The map had no `art`/`stickers`
28
+ // entries, which is why admin could never filter to either type.
33
29
  function buildBaseColumns() {
34
30
  return [
35
31
  {
@@ -112,11 +108,23 @@ export function AdminProductsView({ children, ...props }) {
112
108
  defaultSort: sortBy("createdAt", "DESC"),
113
109
  queryKey: ["admin", "products", "listing"],
114
110
  endpoint: ADMIN_ENDPOINTS.PRODUCTS,
111
+ // "Featured first" / "Promoted first" existed in STANDARD_SORT_OPTIONS but
112
+ // were offered nowhere, and would not have worked if they had been:
113
+ // `featured`/`isPromoted` were `canSort: false`, so sievejs dropped the
114
+ // sort silently. Both are now sortable (products.repository.ts) with
115
+ // matching composite indexes, and admin is where curating them matters.
116
+ //
117
+ // Deliberately a curated set, not the full 10-option STANDARD_SORT_OPTIONS:
118
+ // every additional sort field multiplies out against this view's
119
+ // isSold/status/listingType filter combinations into more composite
120
+ // indexes, and Rule #6 says spend those only where they earn their keep.
115
121
  sortOptions: [
116
122
  { value: sortBy("createdAt", "DESC"), label: "Newest" },
117
123
  { value: sortBy("createdAt", "ASC"), label: "Oldest" },
118
124
  { value: "title", label: "Title A–Z" },
119
125
  { value: sortBy("price", "DESC"), label: "Highest price" },
126
+ { value: sortBy("featured", "DESC"), label: "Featured first" },
127
+ { value: sortBy("isPromoted", "DESC"), label: "Promoted first" },
120
128
  ],
121
129
  columns: [...buildBaseColumns(), flagColumn],
122
130
  mapRows: (response) => toRecordArray(response.items).map((item, index) => {
@@ -165,16 +173,24 @@ export function AdminProductsView({ children, ...props }) {
165
173
  parts.push("isSold==false");
166
174
  if (state.status && state.status !== "All")
167
175
  parts.push(sieveFilter("status", SIEVE_OP.EQ, state.status));
168
- const typeFilter = TYPE_FILTER_MAP[state.type];
169
- if (typeFilter)
170
- parts.push(typeFilter);
171
- // Ended/closed only apply to auctions/prize-draws auctionEndDate
172
- // and prizeRevealWindowEnd do not exist on other listing types, so
173
- // these are no-ops unless the matching Type tab is also selected.
174
- if (typeFilter === "listingType==auction" && state.showEnded !== "true") {
176
+ // `state.type` is a pipe-joined set of canonical ListingType values
177
+ // (multi-select chips). It used to be a single display LABEL resolved
178
+ // through a TYPE_FILTER_MAP lookup — an indirection that silently
179
+ // dropped `art` and `stickers`, which had no map entry.
180
+ const selectedTypes = parseSelectedListingTypes(state.type);
181
+ if (selectedTypes.length > 0) {
182
+ parts.push(sieveFilter("listingType", SIEVE_OP.EQ, selectedTypes.join("|")));
183
+ }
184
+ // Ended/closed are per-type predicates: `auctionEndDate` and
185
+ // `prizeRevealWindowEnd` only exist on auctions and prize-draws. Apply
186
+ // each only when its type is the ONLY one selected — with a mixed
187
+ // selection the inequality would exclude every row of the other types
188
+ // (they have no such field at all), which reads as "no results".
189
+ const onlyType = selectedTypes.length === 1 ? selectedTypes[0] : null;
190
+ if (onlyType === "auction" && state.showEnded !== "true") {
175
191
  parts.push(sieveFilter("auctionEndDate", SIEVE_OP.GTE, new Date().toISOString()));
176
192
  }
177
- if (typeFilter === "listingType==prize-draw" && state.showClosed !== "true") {
193
+ if (onlyType === "prize-draw" && state.showClosed !== "true") {
178
194
  parts.push(sieveFilter("prizeRevealWindowEnd", SIEVE_OP.GTE, new Date().toISOString()));
179
195
  }
180
196
  return parts.join(",") || undefined;
@@ -247,7 +263,7 @@ export function AdminProductsView({ children, ...props }) {
247
263
  submitLabel: "Save",
248
264
  },
249
265
  ] })),
250
- renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_PRODUCT_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Type", tabs: ADMIN_PRODUCT_LISTING_TYPE_TABS, value: pendingFilters.type ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, type: id })) }), _jsx(FilterChipGroup, { label: "Sold", tabs: [
266
+ renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_PRODUCT_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { multiple: true, label: "Type", tabs: ADMIN_PRODUCT_LISTING_TYPE_TABS, value: pendingFilters.type ?? "", onChange: (ids) => setPendingFilters((p) => ({ ...p, type: ids })) }), _jsx(FilterChipGroup, { label: "Sold", tabs: [
251
267
  { id: "", label: "Hide sold" },
252
268
  { id: "true", label: "Show sold" },
253
269
  ], value: pendingFilters.showSold ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, showSold: id })), allId: "" }), _jsx(FilterChipGroup, { label: "Ended", tabs: [
@@ -18,5 +18,5 @@ export function AdminSiteConfigGuideView() {
18
18
  { name: "platformLimits", desc: "Cart item cap (50), wishlist cap (20), history cap (50), support ticket limits (2 general + 1 per order)." },
19
19
  { name: "legalPages", desc: "Body HTML for Terms of Service, Privacy Policy, Refund Policy, and Shipping Policy. Rendered on their respective static pages." },
20
20
  ];
21
- return (_jsxs(Stack, { className: "max-w-3xl mx-auto", padding: "b-2xl", gap: "xl", children: [_jsxs(Section, { children: [_jsxs(Row, { className: "mb-2", align: "center", gap: "3", children: [_jsx(Row, { className: "flex-shrink-0 w-10 h-10 [background:linear-gradient(135deg,var(--appkit-color-primary-700)_0%,var(--appkit-color-cobalt)_100%)]", align: "center", justify: "center", rounded: "xl", children: _jsx(Settings, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] tracking-widest", size: "sm", weight: "semibold", transform: "uppercase", children: "Admin Guide" })] }), _jsx(Heading, { level: 1, className: "text-[var(--appkit-color-text)] mb-2", mdSize: "3xl", size: "2xl", weight: "bold", children: "Site Configuration" }), _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", children: ["All site settings are stored in a single Firestore document at ", _jsx(Code, { size: "xs", children: "site_settings/global" }), ". Changes take effect on the next page render (ISR or cache revalidation)."] })] }), _jsxs(Section, { className: GC.sectionWrap, children: [_jsxs(Div, { className: GC.sectionHeader, children: [_jsx(Settings, { className: "w-5 h-5 text-[var(--appkit-color-primary)]" }), _jsx(Heading, { level: 2, className: GC.sectionTitle, children: "Settings Groups" })] }), _jsx(Stack, { paddingY: "y-md-lg", padding: "x-lg", gap: "md", children: SETTING_GROUPS.map(({ name, desc }) => (_jsxs(Row, { textSize: "sm", gap: "3", children: [_jsx(Text, { className: "flex-shrink-0 font-mono text-[var(--appkit-color-primary)] w-40", size: "xs", weight: "semibold", children: name }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: desc })] }, name))) })] }), _jsxs(Section, { className: GC.sectionWrap, children: [_jsxs(Div, { className: GC.sectionHeader, children: [_jsx(Settings, { className: CLS_HEADER_ICON }), _jsx(Heading, { level: 2, className: GC.sectionTitle, children: "API Keys & Integrations" })] }), _jsx(Div, { paddingY: "y-md-lg", padding: "x-lg", children: _jsxs(Alert, { variant: "warning", children: ["API keys in ", _jsx(Code, { size: "xs", children: "integrations" }), " are stored encrypted. They are masked in the admin UI \u2014 you see ", _jsx(Code, { size: "xs", children: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), " by design. To rotate a key: paste the new value into the field and save. The old key is immediately invalidated on your payment/shipping provider's side \u2014 test in staging first. Never paste API keys into Slack, email, or any external service."] }) })] }), _jsxs(Section, { className: GC.sectionWrap, children: [_jsxs(Div, { className: GC.sectionHeader, children: [_jsx(Settings, { className: "w-5 h-5 text-[var(--appkit-color-primary)]" }), _jsx(Heading, { level: 2, className: GC.sectionTitle, children: "Feature Flags" })] }), _jsxs(Stack, { paddingY: "y-md-lg", padding: "x-lg", gap: "3", children: [_jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: ["Feature flags are boolean toggles under ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "siteSettings" }), ". They control platform-wide features without a code deploy:"] }), _jsxs(Ul, { className: "list-inside text-[var(--appkit-color-text-muted)]", marker: "disc", spacing: "tight", size: "sm", children: [_jsxs(Li, { children: [_jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "enableAuctions" }), " \u2014 globally enable/disable all auction listings."] }), _jsxs(Li, { children: [_jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "enablePreOrders" }), " \u2014 globally enable/disable pre-order creation."] }), _jsxs(Li, { children: [_jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "maintenanceMode" }), " \u2014 shows a maintenance banner and blocks non-admin access."] })] }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Flags are evaluated per-request via ISR cache \u2014 a change may take up to the revalidation interval (3600s default) to propagate to all pages." })] })] })] }));
21
+ return (_jsxs(Stack, { className: "max-w-3xl mx-auto", padding: "b-2xl", gap: "xl", children: [_jsxs(Section, { children: [_jsxs(Row, { className: "mb-2", align: "center", gap: "3", children: [_jsx(Row, { className: "flex-shrink-0 w-10 h-10 [background:linear-gradient(135deg,var(--appkit-color-primary-700)_0%,var(--appkit-color-cobalt)_100%)]", align: "center", justify: "center", rounded: "xl", children: _jsx(Settings, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] tracking-widest", size: "sm", weight: "semibold", transform: "uppercase", children: "Admin Guide" })] }), _jsx(Heading, { level: 1, className: "text-[var(--appkit-color-text)] mb-2", mdSize: "3xl", size: "2xl", weight: "bold", children: "Site Configuration" }), _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", children: ["All site settings are stored in a single Firestore document at ", _jsx(Code, { size: "xs", children: "siteSettings/global" }), ". Changes take effect on the next page render (ISR or cache revalidation)."] })] }), _jsxs(Section, { className: GC.sectionWrap, children: [_jsxs(Div, { className: GC.sectionHeader, children: [_jsx(Settings, { className: "w-5 h-5 text-[var(--appkit-color-primary)]" }), _jsx(Heading, { level: 2, className: GC.sectionTitle, children: "Settings Groups" })] }), _jsx(Stack, { paddingY: "y-md-lg", padding: "x-lg", gap: "md", children: SETTING_GROUPS.map(({ name, desc }) => (_jsxs(Row, { textSize: "sm", gap: "3", children: [_jsx(Text, { className: "flex-shrink-0 font-mono text-[var(--appkit-color-primary)] w-40", size: "xs", weight: "semibold", children: name }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: desc })] }, name))) })] }), _jsxs(Section, { className: GC.sectionWrap, children: [_jsxs(Div, { className: GC.sectionHeader, children: [_jsx(Settings, { className: CLS_HEADER_ICON }), _jsx(Heading, { level: 2, className: GC.sectionTitle, children: "API Keys & Integrations" })] }), _jsx(Div, { paddingY: "y-md-lg", padding: "x-lg", children: _jsxs(Alert, { variant: "warning", children: ["API keys in ", _jsx(Code, { size: "xs", children: "integrations" }), " are stored encrypted. They are masked in the admin UI \u2014 you see ", _jsx(Code, { size: "xs", children: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), " by design. To rotate a key: paste the new value into the field and save. The old key is immediately invalidated on your payment/shipping provider's side \u2014 test in staging first. Never paste API keys into Slack, email, or any external service."] }) })] }), _jsxs(Section, { className: GC.sectionWrap, children: [_jsxs(Div, { className: GC.sectionHeader, children: [_jsx(Settings, { className: "w-5 h-5 text-[var(--appkit-color-primary)]" }), _jsx(Heading, { level: 2, className: GC.sectionTitle, children: "Feature Flags" })] }), _jsxs(Stack, { paddingY: "y-md-lg", padding: "x-lg", gap: "3", children: [_jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: ["Feature flags are boolean toggles under ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "siteSettings" }), ". They control platform-wide features without a code deploy:"] }), _jsxs(Ul, { className: "list-inside text-[var(--appkit-color-text-muted)]", marker: "disc", spacing: "tight", size: "sm", children: [_jsxs(Li, { children: [_jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "enableAuctions" }), " \u2014 globally enable/disable all auction listings."] }), _jsxs(Li, { children: [_jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "enablePreOrders" }), " \u2014 globally enable/disable pre-order creation."] }), _jsxs(Li, { children: [_jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "maintenanceMode" }), " \u2014 shows a maintenance banner and blocks non-admin access."] })] }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Flags are evaluated per-request via ISR cache \u2014 a change may take up to the revalidation interval (3600s default) to propagate to all pages." })] })] })] }));
22
22
  }
@@ -146,6 +146,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
146
146
  const [platformFeePercent, setPlatformFeePercent] = React.useState(5);
147
147
  const [gstPercent, setGstPercent] = React.useState(18);
148
148
  const [minimumTransactionFee, setMinimumTransactionFee] = React.useState(0);
149
+ const [platformFeeMax, setPlatformFeeMax] = React.useState(10);
149
150
  const [gatewayFeePercent, setGatewayFeePercent] = React.useState(2);
150
151
  const [payoutHoldDays, setPayoutHoldDays] = React.useState(7);
151
152
  const [minPayoutAmount, setMinPayoutAmount] = React.useState(100);
@@ -329,6 +330,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
329
330
  setPlatformFeePercent(s.commissions?.platformFeePercent ?? 5);
330
331
  setGstPercent(s.commissions?.gstPercent ?? 18);
331
332
  setMinimumTransactionFee(s.commissions?.minimumTransactionFee ?? 0);
333
+ setPlatformFeeMax(s.commissions?.platformFeeMax ?? 10);
332
334
  setGatewayFeePercent(s.commissions?.gatewayFeePercent ?? 2);
333
335
  setPayoutHoldDays(s.commissions?.payoutHoldDays ?? 7);
334
336
  setMinPayoutAmount(s.commissions?.minPayoutAmount ?? 100);
@@ -522,7 +524,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
522
524
  offsetX: watermarkOffsetX,
523
525
  offsetY: watermarkOffsetY,
524
526
  },
525
- commissions: { platformFeePercent, gstPercent, minimumTransactionFee, gatewayFeePercent, payoutHoldDays, minPayoutAmount, auctionListingFee, preOrderListingFee, featuredSlotFee, promotedSlotFee, whatsappNotifyFeeEnabled, whatsappNotifyFee, giftWrapFeeEnabled, giftWrapFee, shipmentProtectionFeeEnabled, shipmentProtectionFeePercent, shipmentProtectionFeeMin, codDepositPercent, sellerShippingFixed, platformShippingPercent, platformShippingFixedMin },
527
+ commissions: { platformFeePercent, gstPercent, minimumTransactionFee, platformFeeMax, gatewayFeePercent, payoutHoldDays, minPayoutAmount, auctionListingFee, preOrderListingFee, featuredSlotFee, promotedSlotFee, whatsappNotifyFeeEnabled, whatsappNotifyFee, giftWrapFeeEnabled, giftWrapFee, shipmentProtectionFeeEnabled, shipmentProtectionFeePercent, shipmentProtectionFeeMin, codDepositPercent, sellerShippingFixed, platformShippingPercent, platformShippingFixedMin },
526
528
  laborRate: { hourlyRate: laborHourlyRate, maxHoursPerDay: laborMaxHoursPerDay },
527
529
  emi: {
528
530
  enabled: emiEnabled,
@@ -668,7 +670,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
668
670
  { label: "Top right", value: "top-right" },
669
671
  { label: "Bottom left", value: "bottom-left" },
670
672
  { label: "Bottom right", value: "bottom-right" },
671
- ], value: watermarkPosition, onValueChange: (v) => setWatermarkPosition(v), disabled: watermarkUseCustomOffset }), _jsx(Toggle, { checked: watermarkUseCustomOffset, onChange: setWatermarkUseCustomOffset, label: "Use a custom X/Y offset instead" }), watermarkUseCustomOffset && (_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "X offset (%) \u2014 + right / \u2212 left of center", value: String(watermarkOffsetX), onChange: (e) => setWatermarkOffsetX(Math.max(-45, Math.min(45, parseInt(e.target.value) || 0))), type: "number", min: -45, max: 45 }), _jsx(Input, { label: "Y offset (%) \u2014 + down / \u2212 up from center", value: String(watermarkOffsetY), onChange: (e) => setWatermarkOffsetY(Math.max(-45, Math.min(45, parseInt(e.target.value) || 0))), type: "number", min: -45, max: 45 })] })), _jsxs(Stack, { gap: "xs", surface: "muted", rounded: "lg", border: "default", padding: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Preview (text watermark only)" }), _jsx(Row, { surface: "default", justify: "end", align: "end", className: `relative h-32 ${__O.hidden}`, rounded: "default", children: _jsx(Span, { ref: watermarkPreviewRef, weight: "medium", color: "faint", padding: "1", className: "select-none", children: watermarkText }) })] })] }) }), _jsx(TabsContent, { value: "fees", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Platform fee \u2014 our cut (%)", helperText: "% charged on order value. Buyer pays this.", value: String(platformFeePercent), onChange: (e) => setPlatformFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "GST on platform fee (%)", helperText: "Applied to our fee only (not full order). Usually 18%.", value: String(gstPercent), onChange: (e) => setGstPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "Razorpay gateway cost (%)", helperText: "Gateway's own fee \u2014 absorbed by platform, not passed through.", value: String(gatewayFeePercent), onChange: (e) => setGatewayFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 10, step: 0.01 }), _jsx(Input, { label: "Minimum transaction fee (\u20B9)", helperText: "Per-transaction floor. Total charge will never be below base + this.", value: String(minimumTransactionFee), onChange: (e) => setMinimumTransactionFee(parseFloat(e.target.value) || 0), type: "number", min: 0, step: 0.01 }), _jsx(Input, { label: "Seller payout hold (days)", value: String(payoutHoldDays), onChange: (e) => setPayoutHoldDays(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Minimum payout amount (\u20B9)", value: String(minPayoutAmount), onChange: (e) => setMinPayoutAmount(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Auction listing fee (\u20B9)", value: String(auctionListingFee), onChange: (e) => setAuctionListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Pre-order listing fee (\u20B9)", value: String(preOrderListingFee), onChange: (e) => setPreOrderListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Featured slot fee (\u20B9)", value: String(featuredSlotFee), onChange: (e) => setFeaturedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Promoted slot fee (\u20B9)", value: String(promotedSlotFee), onChange: (e) => setPromotedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "COD deposit (%)", helperText: "% of order collected upfront on COD orders; remainder paid on delivery.", value: String(codDepositPercent), onChange: (e) => setCodDepositPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 })] }), _jsx(Toggle, { checked: whatsappNotifyFeeEnabled, onChange: setWhatsappNotifyFeeEnabled, label: "Offer the WhatsApp order-updates addon at checkout" }), _jsx(Grid, { cols: 2, gap: "md", children: _jsx(Input, { label: "WhatsApp addon fee (\u20B9)", helperText: "Flat fee charged to the buyer when they opt in. Requires WhatsApp credentials + at least one approved template \u2014 see the WhatsApp tab.", value: String(whatsappNotifyFee), onChange: (e) => setWhatsappNotifyFee(parseFloat(e.target.value) || 0), type: "number", min: 0, disabled: !whatsappNotifyFeeEnabled }) }), _jsx(Toggle, { checked: giftWrapFeeEnabled, onChange: setGiftWrapFeeEnabled, label: "Offer the gift-wrap addon at checkout" }), _jsx(Grid, { cols: 2, gap: "md", children: _jsx(Input, { label: "Gift wrap fee (\u20B9)", helperText: "Flat fee charged to the buyer when they opt in.", value: String(giftWrapFee), onChange: (e) => setGiftWrapFee(parseFloat(e.target.value) || 0), type: "number", min: 0, disabled: !giftWrapFeeEnabled }) }), _jsx(Toggle, { checked: shipmentProtectionFeeEnabled, onChange: setShipmentProtectionFeeEnabled, label: "Offer the shipment-protection addon at checkout" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Shipment protection (%)", helperText: "% of order subtotal, charged when the buyer opts in.", value: String(shipmentProtectionFeePercent), onChange: (e) => setShipmentProtectionFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1, disabled: !shipmentProtectionFeeEnabled }), _jsx(Input, { label: "Shipment protection minimum (\u20B9)", helperText: "Floor for the shipment-protection fee.", value: String(shipmentProtectionFeeMin), onChange: (e) => setShipmentProtectionFeeMin(parseFloat(e.target.value) || 0), type: "number", min: 0, disabled: !shipmentProtectionFeeEnabled })] })] }) }), _jsx(TabsContent, { value: "procurement", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Used to compute each procurement shipment's labor cost (hours spent \u00D7 hourly rate) and its estimated processing time (hours spent \u00F7 max hours/day)." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Hourly rate (\u20B9)", helperText: "Your effective hourly wage for sorting/categorizing/listing a shipment.", value: String(laborHourlyRate), onChange: (e) => setLaborHourlyRate(Math.round((parseFloat(e.target.value) || 0) * 100) / 100), type: "number", min: 0 }), _jsx(Input, { label: "Max hours per day", helperText: "Used only to project how many days a shipment's tracked hours will take.", value: String(laborMaxHoursPerDay), onChange: (e) => setLaborMaxHoursPerDay(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 24 })] })] }) }), _jsx(TabsContent, { value: "emi", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "A seller must also opt in via their own Payout Settings for EMI to appear at checkout on their items. See the \"How EMI Works\" public page for buyer-facing copy." }), _jsx(Toggle, { checked: emiEnabled, onChange: setEmiEnabled, label: "Enable EMI platform-wide" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Minimum order value (\u20B9)", helperText: "A seller's cart subtotal must exceed this for EMI to appear as an option.", value: String(emiMinOrderValue), onChange: (e) => setEmiMinOrderValue(Math.round((parseFloat(e.target.value) || 0) * 100) / 100), type: "number", min: 0 }), _jsx(Input, { label: "Tenure options (months, comma-separated)", helperText: "e.g. 2,3,4,5,6", value: emiTenureOptionsText, onChange: (e) => setEmiTenureOptionsText(e.target.value) }), _jsx(Input, { label: "Token / down-payment (%)", helperText: "Collected upfront at checkout.", value: String(emiTokenPercent), onChange: (e) => setEmiTokenPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100 }), _jsx(Input, { label: "Billing day (1\u201310)", helperText: "Day of month each installment is due.", value: String(emiBillingDay), onChange: (e) => setEmiBillingDay(parseFloat(e.target.value) || 1), type: "number", min: 1, max: 10 }), _jsx(Input, { label: "Surcharge (% of principal per month)", helperText: "The 'excess EMI fee' the buyer pays for spreading payment.", value: String(emiSurchargePercentPerMonth), onChange: (e) => setEmiSurchargePercentPerMonth(parseFloat(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Seller's share of surcharge (%)", helperText: "The rest of the surcharge goes to the platform.", value: String(emiSurchargeSellerSharePercent), onChange: (e) => setEmiSurchargeSellerSharePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100 })] })] }) }), _jsx(TabsContent, { value: "gst", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Required before enabling GST-inclusive checkout (P-8). Also set a GST rate + HSN code on each taxable product for the tax breakdown to appear." }), _jsx(Toggle, { checked: gstEnabled, onChange: setGstEnabled, label: "Enable GST on checkout" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "GSTIN", helperText: "15-character GST Identification Number.", value: gstin, onChange: (e) => setGstin(e.target.value), placeholder: "29AAAAA0000A1Z5", maxLength: 15 }), _jsx(Input, { label: "Legal name", helperText: "Registered business name for invoices.", value: gstLegalName, onChange: (e) => setGstLegalName(e.target.value), placeholder: "LetItRip Collectibles Pvt Ltd" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Registered address" }), _jsx(Textarea, { value: gstAddress, onChange: (e) => setGstAddress(e.target.value), placeholder: "Registered business address for GST invoices\u2026", rows: 3 })] })] }) }), _jsx(TabsContent, { value: "featureflags", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "lg", children: [_jsx(Text, { size: "xs", color: "muted", children: "Site-wide platform feature toggles. Disabling a flag hides that feature's nav entries and pages \u2014 existing data is untouched, it just becomes unreachable until re-enabled." }), _jsx(Grid, { cols: 2, gap: "md", children: FEATURE_FLAG_META.map((flag) => (_jsxs(Stack, { gap: "xs", children: [_jsx(Toggle, { label: `${flag.icon} ${flag.labelKey}`, checked: featureFlags[flag.key] ?? false, onChange: (v) => setFeatureFlags((prev) => ({ ...prev, [flag.key]: v })) }), _jsx(Text, { size: "xs", color: "muted", children: flag.descKey })] }, flag.key))) }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Listing types" }), _jsx(Text, { size: "xs", color: "muted", children: "Disabled types are hidden from listings and reject create/add-to-cart." }), _jsx(Grid, { cols: 2, gap: "sm", children: LISTING_TYPE_KEYS.map((key) => (_jsx(Toggle, { label: key, checked: listingTypeFlags[key] ?? true, onChange: (v) => setListingTypeFlags((prev) => ({ ...prev, [key]: v })) }, key))) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Category types" }), _jsx(Grid, { cols: 2, gap: "sm", children: CATEGORY_TYPE_KEYS.map((key) => (_jsx(Toggle, { label: key, checked: categoryTypeFlags[key] ?? true, onChange: (v) => setCategoryTypeFlags((prev) => ({ ...prev, [key]: v })) }, key))) })] })] }) }), _jsx(TabsContent, { value: "integrations", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Keys are masked in transit and stored encrypted. Click Reveal to view." }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Razorpay" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Razorpay Key ID", value: razorpayKeyId, onChange: setRazorpayKeyId, placeholder: "rzp_live_\u2026" }), _jsx(MaskedInput, { label: "Razorpay Secret", value: razorpaySecret, onChange: setRazorpaySecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "SMTP / Email" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "SMTP host", value: smtpHost, onChange: (e) => setSmtpHost(e.target.value), placeholder: "smtp.sendgrid.net" }), _jsx(Input, { label: "SMTP port", value: smtpPort, onChange: (e) => setSmtpPort(e.target.value), type: "number", placeholder: "587" }), _jsx(Input, { label: "SMTP user", value: smtpUser, onChange: (e) => setSmtpUser(e.target.value), placeholder: "apikey" }), _jsx(MaskedInput, { label: "SMTP password", value: smtpPassword, onChange: setSmtpPassword, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), _jsx(Input, { label: "From address", value: smtpFrom, onChange: (e) => setSmtpFrom(e.target.value), placeholder: "noreply@letitrip.in", type: "email" })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Analytics & Tracking" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Google Analytics ID", value: gaMeasurementId, onChange: (e) => setGaMeasurementId(e.target.value), placeholder: "G-XXXXXXXXXX" }), _jsx(Input, { label: "Facebook Pixel ID", value: fbPixelId, onChange: (e) => setFbPixelId(e.target.value), placeholder: "XXXXXXXXXXXXXXXX" }), _jsx(Input, { label: "GTM Container ID", value: gtmContainerId, onChange: (e) => setGtmContainerId(e.target.value), placeholder: "GTM-XXXXXXX" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Meta (Instagram & Facebook Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Used by the Social Feed section to fetch Instagram and Facebook posts via Meta Graph API v19." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Page Access Token", value: metaPageAccessToken, onChange: setMetaPageAccessToken, placeholder: "EAAxxxxxxx\u2026" }), _jsx(Input, { label: "Page ID (or handle)", value: metaPageId, onChange: (e) => setMetaPageId(e.target.value), placeholder: "letitrip" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "TikTok for Developers (Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Client credentials + long-lived access token from TikTok for Developers. Used to list your account's public videos." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Client Key", value: tiktokClientKey, onChange: setTiktokClientKey, placeholder: "aw\u2026" }), _jsx(MaskedInput, { label: "Client Secret", value: tiktokClientSecret, onChange: setTiktokClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(MaskedInput, { label: "Access Token (long-lived)", value: tiktokAccessToken, onChange: setTiktokAccessToken, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "DeviantArt OAuth2 (Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Client credentials for DeviantArt gallery fetching (client-credentials OAuth2 flow \u2014 no user login required)." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Client ID", value: deviantartClientId, onChange: setDeviantartClientId, placeholder: "1234" }), _jsx(MaskedInput, { label: "Client Secret", value: deviantartClientSecret, onChange: setDeviantartClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] })] }) }), _jsx(TabsContent, { value: "shipping", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Payment methods" }), _jsx(Toggle, { label: "Razorpay (online card/UPI) enabled \u2014 disabled by default, manual payment is the default", checked: razorpayEnabled, onChange: setRazorpayEnabled }), _jsx(Toggle, { label: "Manual UPI/bank transfer enabled", checked: upiManualEnabled, onChange: setUpiManualEnabled }), _jsx(Toggle, { label: "Cash on delivery (COD) enabled", checked: codEnabled, onChange: setCodEnabled }), _jsx(Input, { label: "Free shipping threshold (\u20B9)", value: String(freeShippingThreshold), onChange: (e) => setFreeShippingThreshold(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Orders above this amount get free shipping." }), _jsx(Select, { label: "Default carrier", options: CARRIER_OPTIONS, value: defaultCarrier, onValueChange: setDefaultCarrier }), _jsx(Input, { label: "Max delivery radius (km, 0 = no limit)", value: String(maxDeliveryRadius), onChange: (e) => setMaxDeliveryRadius(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "High-value checkout OTP threshold (\u20B9)", helperText: "Carts at or above this subtotal require an extra OTP verification step before payment (0 = disabled).", value: String(otpCheckoutThreshold), onChange: (e) => setOtpCheckoutThreshold(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Text, { size: "sm", weight: "medium", color: "muted", className: "pt-[var(--appkit-space-2)]", children: "Seller shipping charge defaults" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Seller shipping fixed (\u20B9)", helperText: "Flat shipping charge collected on the seller's behalf.", value: String(sellerShippingFixed), onChange: (e) => setSellerShippingFixed(parseFloat(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Platform shipping (%)", helperText: "Platform's own shipping markup, as a % of order value.", value: String(platformShippingPercent), onChange: (e) => setPlatformShippingPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "Platform shipping minimum (\u20B9)", helperText: "Floor for the platform shipping markup.", value: String(platformShippingFixedMin), onChange: (e) => setPlatformShippingFixedMin(parseFloat(e.target.value) || 0), type: "number", min: 0 })] })] }) }), _jsx(TabsContent, { value: "auction", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "sm", children: [_jsx(Text, { size: "xs", weight: "medium", color: "muted", children: "Bid increment tiers" }), _jsx(Text, { size: "xs", color: "muted", children: "The minimum jump a new bid must clear, based on the current bid amount. A seller's per-listing override can require more than the matching tier, but can never undercut it." }), bidIncrementTiers.map((tier, i) => {
673
+ ], value: watermarkPosition, onValueChange: (v) => setWatermarkPosition(v), disabled: watermarkUseCustomOffset }), _jsx(Toggle, { checked: watermarkUseCustomOffset, onChange: setWatermarkUseCustomOffset, label: "Use a custom X/Y offset instead" }), watermarkUseCustomOffset && (_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "X offset (%) \u2014 + right / \u2212 left of center", value: String(watermarkOffsetX), onChange: (e) => setWatermarkOffsetX(Math.max(-45, Math.min(45, parseInt(e.target.value) || 0))), type: "number", min: -45, max: 45 }), _jsx(Input, { label: "Y offset (%) \u2014 + down / \u2212 up from center", value: String(watermarkOffsetY), onChange: (e) => setWatermarkOffsetY(Math.max(-45, Math.min(45, parseInt(e.target.value) || 0))), type: "number", min: -45, max: 45 })] })), _jsxs(Stack, { gap: "xs", surface: "muted", rounded: "lg", border: "default", padding: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Preview (text watermark only)" }), _jsx(Row, { surface: "default", justify: "end", align: "end", className: `relative h-32 ${__O.hidden}`, rounded: "default", children: _jsx(Span, { ref: watermarkPreviewRef, weight: "medium", color: "faint", padding: "1", className: "select-none", children: watermarkText }) })] })] }) }), _jsx(TabsContent, { value: "fees", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Platform fee \u2014 our cut (%)", helperText: "% charged on order value. Buyer pays this.", value: String(platformFeePercent), onChange: (e) => setPlatformFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "GST on platform fee (%)", helperText: "Applied to our fee only (not full order). Usually 18%.", value: String(gstPercent), onChange: (e) => setGstPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "Razorpay gateway cost (%)", helperText: "Gateway's own fee \u2014 absorbed by platform, not passed through.", value: String(gatewayFeePercent), onChange: (e) => setGatewayFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 10, step: 0.01 }), _jsx(Input, { label: "Minimum transaction fee (\u20B9)", helperText: "Per-transaction floor. Total charge will never be below base + this.", value: String(minimumTransactionFee), onChange: (e) => setMinimumTransactionFee(parseFloat(e.target.value) || 0), type: "number", min: 0, step: 0.01 }), _jsx(Input, { label: "Maximum platform fee (\u20B9)", helperText: "Ceiling on the buyer's platform commission, charged once per checkout on every payment method. GST is calculated on the capped amount.", value: String(platformFeeMax), onChange: (e) => setPlatformFeeMax(parseFloat(e.target.value) || 0), type: "number", min: 0, step: 0.01 }), _jsx(Input, { label: "Seller payout hold (days)", value: String(payoutHoldDays), onChange: (e) => setPayoutHoldDays(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Minimum payout amount (\u20B9)", value: String(minPayoutAmount), onChange: (e) => setMinPayoutAmount(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Auction listing fee (\u20B9)", value: String(auctionListingFee), onChange: (e) => setAuctionListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Pre-order listing fee (\u20B9)", value: String(preOrderListingFee), onChange: (e) => setPreOrderListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Featured slot fee (\u20B9)", value: String(featuredSlotFee), onChange: (e) => setFeaturedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Promoted slot fee (\u20B9)", value: String(promotedSlotFee), onChange: (e) => setPromotedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "COD deposit (%)", helperText: "% of order collected upfront on COD orders; remainder paid on delivery.", value: String(codDepositPercent), onChange: (e) => setCodDepositPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 })] }), _jsx(Toggle, { checked: whatsappNotifyFeeEnabled, onChange: setWhatsappNotifyFeeEnabled, label: "Offer the WhatsApp order-updates addon at checkout" }), _jsx(Grid, { cols: 2, gap: "md", children: _jsx(Input, { label: "WhatsApp addon fee (\u20B9)", helperText: "Flat fee charged to the buyer when they opt in. Requires WhatsApp credentials + at least one approved template \u2014 see the WhatsApp tab.", value: String(whatsappNotifyFee), onChange: (e) => setWhatsappNotifyFee(parseFloat(e.target.value) || 0), type: "number", min: 0, disabled: !whatsappNotifyFeeEnabled }) }), _jsx(Toggle, { checked: giftWrapFeeEnabled, onChange: setGiftWrapFeeEnabled, label: "Offer the gift-wrap addon at checkout" }), _jsx(Grid, { cols: 2, gap: "md", children: _jsx(Input, { label: "Gift wrap fee (\u20B9)", helperText: "Flat fee charged to the buyer when they opt in.", value: String(giftWrapFee), onChange: (e) => setGiftWrapFee(parseFloat(e.target.value) || 0), type: "number", min: 0, disabled: !giftWrapFeeEnabled }) }), _jsx(Toggle, { checked: shipmentProtectionFeeEnabled, onChange: setShipmentProtectionFeeEnabled, label: "Offer the shipment-protection addon at checkout" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Shipment protection (%)", helperText: "% of order subtotal, charged when the buyer opts in.", value: String(shipmentProtectionFeePercent), onChange: (e) => setShipmentProtectionFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1, disabled: !shipmentProtectionFeeEnabled }), _jsx(Input, { label: "Shipment protection minimum (\u20B9)", helperText: "Floor for the shipment-protection fee.", value: String(shipmentProtectionFeeMin), onChange: (e) => setShipmentProtectionFeeMin(parseFloat(e.target.value) || 0), type: "number", min: 0, disabled: !shipmentProtectionFeeEnabled })] })] }) }), _jsx(TabsContent, { value: "procurement", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Used to compute each procurement shipment's labor cost (hours spent \u00D7 hourly rate) and its estimated processing time (hours spent \u00F7 max hours/day)." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Hourly rate (\u20B9)", helperText: "Your effective hourly wage for sorting/categorizing/listing a shipment.", value: String(laborHourlyRate), onChange: (e) => setLaborHourlyRate(Math.round((parseFloat(e.target.value) || 0) * 100) / 100), type: "number", min: 0 }), _jsx(Input, { label: "Max hours per day", helperText: "Used only to project how many days a shipment's tracked hours will take.", value: String(laborMaxHoursPerDay), onChange: (e) => setLaborMaxHoursPerDay(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 24 })] })] }) }), _jsx(TabsContent, { value: "emi", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "A seller must also opt in via their own Payout Settings for EMI to appear at checkout on their items. See the \"How EMI Works\" public page for buyer-facing copy." }), _jsx(Toggle, { checked: emiEnabled, onChange: setEmiEnabled, label: "Enable EMI platform-wide" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Minimum order value (\u20B9)", helperText: "A seller's cart subtotal must exceed this for EMI to appear as an option.", value: String(emiMinOrderValue), onChange: (e) => setEmiMinOrderValue(Math.round((parseFloat(e.target.value) || 0) * 100) / 100), type: "number", min: 0 }), _jsx(Input, { label: "Tenure options (months, comma-separated)", helperText: "e.g. 2,3,4,5,6", value: emiTenureOptionsText, onChange: (e) => setEmiTenureOptionsText(e.target.value) }), _jsx(Input, { label: "Token / down-payment (%)", helperText: "Collected upfront at checkout.", value: String(emiTokenPercent), onChange: (e) => setEmiTokenPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100 }), _jsx(Input, { label: "Billing day (1\u201310)", helperText: "Day of month each installment is due.", value: String(emiBillingDay), onChange: (e) => setEmiBillingDay(parseFloat(e.target.value) || 1), type: "number", min: 1, max: 10 }), _jsx(Input, { label: "Surcharge (% of principal per month)", helperText: "The 'excess EMI fee' the buyer pays for spreading payment.", value: String(emiSurchargePercentPerMonth), onChange: (e) => setEmiSurchargePercentPerMonth(parseFloat(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Seller's share of surcharge (%)", helperText: "The rest of the surcharge goes to the platform.", value: String(emiSurchargeSellerSharePercent), onChange: (e) => setEmiSurchargeSellerSharePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100 })] })] }) }), _jsx(TabsContent, { value: "gst", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Required before enabling GST-inclusive checkout (P-8). Also set a GST rate + HSN code on each taxable product for the tax breakdown to appear." }), _jsx(Toggle, { checked: gstEnabled, onChange: setGstEnabled, label: "Enable GST on checkout" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "GSTIN", helperText: "15-character GST Identification Number.", value: gstin, onChange: (e) => setGstin(e.target.value), placeholder: "29AAAAA0000A1Z5", maxLength: 15 }), _jsx(Input, { label: "Legal name", helperText: "Registered business name for invoices.", value: gstLegalName, onChange: (e) => setGstLegalName(e.target.value), placeholder: "LetItRip Collectibles Pvt Ltd" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Registered address" }), _jsx(Textarea, { value: gstAddress, onChange: (e) => setGstAddress(e.target.value), placeholder: "Registered business address for GST invoices\u2026", rows: 3 })] })] }) }), _jsx(TabsContent, { value: "featureflags", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "lg", children: [_jsx(Text, { size: "xs", color: "muted", children: "Site-wide platform feature toggles. Disabling a flag hides that feature's nav entries and pages \u2014 existing data is untouched, it just becomes unreachable until re-enabled." }), _jsx(Grid, { cols: 2, gap: "md", children: FEATURE_FLAG_META.map((flag) => (_jsxs(Stack, { gap: "xs", children: [_jsx(Toggle, { label: `${flag.icon} ${flag.labelKey}`, checked: featureFlags[flag.key] ?? false, onChange: (v) => setFeatureFlags((prev) => ({ ...prev, [flag.key]: v })) }), _jsx(Text, { size: "xs", color: "muted", children: flag.descKey })] }, flag.key))) }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Listing types" }), _jsx(Text, { size: "xs", color: "muted", children: "Disabled types are hidden from listings and reject create/add-to-cart." }), _jsx(Grid, { cols: 2, gap: "sm", children: LISTING_TYPE_KEYS.map((key) => (_jsx(Toggle, { label: key, checked: listingTypeFlags[key] ?? true, onChange: (v) => setListingTypeFlags((prev) => ({ ...prev, [key]: v })) }, key))) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Category types" }), _jsx(Grid, { cols: 2, gap: "sm", children: CATEGORY_TYPE_KEYS.map((key) => (_jsx(Toggle, { label: key, checked: categoryTypeFlags[key] ?? true, onChange: (v) => setCategoryTypeFlags((prev) => ({ ...prev, [key]: v })) }, key))) })] })] }) }), _jsx(TabsContent, { value: "integrations", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Keys are masked in transit and stored encrypted. Click Reveal to view." }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Razorpay" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Razorpay Key ID", value: razorpayKeyId, onChange: setRazorpayKeyId, placeholder: "rzp_live_\u2026" }), _jsx(MaskedInput, { label: "Razorpay Secret", value: razorpaySecret, onChange: setRazorpaySecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "SMTP / Email" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "SMTP host", value: smtpHost, onChange: (e) => setSmtpHost(e.target.value), placeholder: "smtp.sendgrid.net" }), _jsx(Input, { label: "SMTP port", value: smtpPort, onChange: (e) => setSmtpPort(e.target.value), type: "number", placeholder: "587" }), _jsx(Input, { label: "SMTP user", value: smtpUser, onChange: (e) => setSmtpUser(e.target.value), placeholder: "apikey" }), _jsx(MaskedInput, { label: "SMTP password", value: smtpPassword, onChange: setSmtpPassword, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), _jsx(Input, { label: "From address", value: smtpFrom, onChange: (e) => setSmtpFrom(e.target.value), placeholder: "noreply@letitrip.in", type: "email" })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Analytics & Tracking" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Google Analytics ID", value: gaMeasurementId, onChange: (e) => setGaMeasurementId(e.target.value), placeholder: "G-XXXXXXXXXX" }), _jsx(Input, { label: "Facebook Pixel ID", value: fbPixelId, onChange: (e) => setFbPixelId(e.target.value), placeholder: "XXXXXXXXXXXXXXXX" }), _jsx(Input, { label: "GTM Container ID", value: gtmContainerId, onChange: (e) => setGtmContainerId(e.target.value), placeholder: "GTM-XXXXXXX" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Meta (Instagram & Facebook Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Used by the Social Feed section to fetch Instagram and Facebook posts via Meta Graph API v19." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Page Access Token", value: metaPageAccessToken, onChange: setMetaPageAccessToken, placeholder: "EAAxxxxxxx\u2026" }), _jsx(Input, { label: "Page ID (or handle)", value: metaPageId, onChange: (e) => setMetaPageId(e.target.value), placeholder: "letitrip" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "TikTok for Developers (Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Client credentials + long-lived access token from TikTok for Developers. Used to list your account's public videos." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Client Key", value: tiktokClientKey, onChange: setTiktokClientKey, placeholder: "aw\u2026" }), _jsx(MaskedInput, { label: "Client Secret", value: tiktokClientSecret, onChange: setTiktokClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(MaskedInput, { label: "Access Token (long-lived)", value: tiktokAccessToken, onChange: setTiktokAccessToken, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "DeviantArt OAuth2 (Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Client credentials for DeviantArt gallery fetching (client-credentials OAuth2 flow \u2014 no user login required)." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Client ID", value: deviantartClientId, onChange: setDeviantartClientId, placeholder: "1234" }), _jsx(MaskedInput, { label: "Client Secret", value: deviantartClientSecret, onChange: setDeviantartClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] })] }) }), _jsx(TabsContent, { value: "shipping", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Payment methods" }), _jsx(Toggle, { label: "Razorpay (online card/UPI) enabled \u2014 disabled by default, manual payment is the default", checked: razorpayEnabled, onChange: setRazorpayEnabled }), _jsx(Toggle, { label: "Manual UPI/bank transfer enabled", checked: upiManualEnabled, onChange: setUpiManualEnabled }), _jsx(Toggle, { label: "Cash on delivery (COD) enabled", checked: codEnabled, onChange: setCodEnabled }), _jsx(Input, { label: "Free shipping threshold (\u20B9)", value: String(freeShippingThreshold), onChange: (e) => setFreeShippingThreshold(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Orders above this amount get free shipping." }), _jsx(Select, { label: "Default carrier", options: CARRIER_OPTIONS, value: defaultCarrier, onValueChange: setDefaultCarrier }), _jsx(Input, { label: "Max delivery radius (km, 0 = no limit)", value: String(maxDeliveryRadius), onChange: (e) => setMaxDeliveryRadius(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "High-value checkout OTP threshold (\u20B9)", helperText: "Carts at or above this subtotal require an extra OTP verification step before payment (0 = disabled).", value: String(otpCheckoutThreshold), onChange: (e) => setOtpCheckoutThreshold(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Text, { size: "sm", weight: "medium", color: "muted", className: "pt-[var(--appkit-space-2)]", children: "Seller shipping charge defaults" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Seller shipping fixed (\u20B9)", helperText: "Flat shipping charge collected on the seller's behalf.", value: String(sellerShippingFixed), onChange: (e) => setSellerShippingFixed(parseFloat(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Platform shipping (%)", helperText: "Platform's own shipping markup, as a % of order value.", value: String(platformShippingPercent), onChange: (e) => setPlatformShippingPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "Platform shipping minimum (\u20B9)", helperText: "Floor for the platform shipping markup.", value: String(platformShippingFixedMin), onChange: (e) => setPlatformShippingFixedMin(parseFloat(e.target.value) || 0), type: "number", min: 0 })] })] }) }), _jsx(TabsContent, { value: "auction", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "sm", children: [_jsx(Text, { size: "xs", weight: "medium", color: "muted", children: "Bid increment tiers" }), _jsx(Text, { size: "xs", color: "muted", children: "The minimum jump a new bid must clear, based on the current bid amount. A seller's per-listing override can require more than the matching tier, but can never undercut it." }), bidIncrementTiers.map((tier, i) => {
672
674
  const isLast = i === bidIncrementTiers.length - 1;
673
675
  return (_jsxs(Grid, { align: "end", gap: "xs", className: "grid-cols-12", children: [_jsx(Div, { className: "col-span-5", children: isLast ? (_jsxs(_Fragment, { children: [_jsx(Text, { size: "xs", weight: "medium", className: "mb-1", children: "Up to (\u20B9)" }), _jsx(Text, { size: "sm", color: "muted", children: "and above" })] })) : (_jsx(Input, { label: "Up to (\u20B9)", type: "number", min: 1, value: String(tier.upTo ?? ""), onChange: (e) => setBidIncrementTiers(bidIncrementTiers.map((t, j) => j === i ? { ...t, upTo: parseInt(e.target.value) || 0 } : t)) })) }), _jsx(Div, { className: "col-span-5", children: _jsx(Input, { label: "Increment (\u20B9)", type: "number", min: 1, value: String(tier.increment), onChange: (e) => setBidIncrementTiers(bidIncrementTiers.map((t, j) => j === i ? { ...t, increment: parseInt(e.target.value) || 0 } : t)) }) }), _jsx(Row, { centered: true, className: "col-span-2", padding: "b-xs", children: _jsx(Button, { variant: "ghost", type: "button", disabled: bidIncrementTiers.length <= 1, onClick: () => setBidIncrementTiers(bidIncrementTiers.filter((_, j) => j !== i)), "aria-label": "Remove tier", children: "\u00D7" }) })] }, i));
674
676
  }), _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setBidIncrementTiers([
@@ -1,3 +1,12 @@
1
+ /**
2
+ * AdminStickersView — admin browse of stickers listings.
3
+ *
4
+ * A thin wrapper over the shared per-listing-type config. This file (and its
5
+ * four siblings) used to carry a full hand-written ListingViewConfig; all five
6
+ * were the same config with a different listingType, and all five shipped
7
+ * an empty filterKeys array — no filter drawer at all — plus their own copy
8
+ * of a three-option sort array. See listing-type-listing-config.ts.
9
+ */
1
10
  import React from "react";
2
11
  import type { ListingLayoutProps } from "../../../ui";
3
12
  export type AdminStickersViewProps = ListingLayoutProps;
@@ -1,50 +1,26 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  /**
4
- * AdminStickersView — admin browse of sticker listings (EMI/art-stickers session).
5
- * Mirrors AdminClassifiedView's thin config-driven pattern.
4
+ * AdminStickersView — admin browse of stickers listings.
5
+ *
6
+ * A thin wrapper over the shared per-listing-type config. This file (and its
7
+ * four siblings) used to carry a full hand-written ListingViewConfig; all five
8
+ * were the same config with a different listingType, and all five shipped
9
+ * an empty filterKeys array — no filter drawer at all — plus their own copy
10
+ * of a three-option sort array. See listing-type-listing-config.ts.
6
11
  */
7
- import { sortBy } from "@mohasinac/appkit/client";
8
12
  import React from "react";
9
13
  import { ListingLayout } from "../../../ui";
10
- import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
11
- import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../hooks/useAdminListingData";
14
+ import { buildListingTypeListingConfig } from "../../products/config/listing-type-listing-config";
12
15
  import { DataListingView } from "./DataListingView";
13
- import { ROUTES } from "../../../next/routing/route-map";
14
- const ADMIN_STICKERS_CONFIG = {
15
- portal: "admin",
16
+ const CONFIG = buildListingTypeListingConfig("stickers", {
16
17
  title: "Stickers",
17
18
  searchPlaceholder: "Search stickers by name or seller",
18
19
  emptyLabel: "No sticker listings",
19
- filterKeys: [],
20
- defaultSort: sortBy("createdAt", "DESC"),
21
- queryKey: ["admin", "stickers", "listing"],
22
- endpoint: ADMIN_ENDPOINTS.PRODUCTS,
23
- sortOptions: [
24
- { value: sortBy("createdAt", "DESC"), label: "Newest" },
25
- { value: sortBy("createdAt", "ASC"), label: "Oldest" },
26
- { value: "title", label: "Title A–Z" },
27
- ],
28
- mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
29
- id: toStringValue(item.id, `stickers-${index}`),
30
- primary: toStringValue(item.title ?? item.productTitle, "Untitled sticker listing"),
31
- secondary: [
32
- toStringValue(item.sellerName, "Unknown seller"),
33
- toCurrency(item.price),
34
- ].join(" · "),
35
- status: toStringValue(item.status, "draft"),
36
- updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
37
- image: toStringValue(item.mainImage, "") || undefined,
38
- })),
39
- getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
40
- buildFilters: () => "listingType==stickers",
41
- // Sticker listings are products (filtered by listingType) — reuse the
42
- // real admin product edit page rather than leaving rows non-navigable.
43
- rowHrefTemplate: String(ROUTES.ADMIN.PRODUCTS_EDIT("{id}")),
44
- };
20
+ });
45
21
  export function AdminStickersView({ children, ...props }) {
46
22
  if (React.Children.count(children) > 0) {
47
23
  return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
48
24
  }
49
- return _jsx(DataListingView, { config: ADMIN_STICKERS_CONFIG });
25
+ return _jsx(DataListingView, { config: CONFIG });
50
26
  }
@@ -38,32 +38,49 @@ export declare const ADMIN_PRODUCT_STATUS_TABS: readonly [{
38
38
  readonly id: "archived";
39
39
  readonly label: "Archived";
40
40
  }];
41
- /** Admin > Products — listing-type filter chip set. Legacy label-as-id IDs
42
- * preserved so the existing Sieve filter values keep matching. */
41
+ /**
42
+ * Admin > Products listing-type filter chip set.
43
+ *
44
+ * IDs are canonical `ListingType` values (2026-08-21). They used to be display
45
+ * LABELS ("Products", "Pre-orders", …) translated to a Sieve clause through a
46
+ * `TYPE_FILTER_MAP` lookup in AdminProductsView — an indirection that hid two
47
+ * whole missing types (`art`, `stickers`) and put the array outside the reach
48
+ * of `audit-filter-tab-enums.mjs`, which compares chip ids against the real
49
+ * backing enum. Now registered there, so a missing or misspelled type blocks.
50
+ *
51
+ * Multi-select: this feeds a `<FilterChipGroup multiple>`, so several types can
52
+ * be active at once and the selection is emitted as a pipe-joined OR-group.
53
+ */
43
54
  export declare const ADMIN_PRODUCT_LISTING_TYPE_TABS: readonly [{
44
55
  readonly id: "All";
45
56
  readonly label: "All";
46
57
  }, {
47
- readonly id: "Products";
58
+ readonly id: "standard";
48
59
  readonly label: "Products";
49
60
  }, {
50
- readonly id: "Auctions";
61
+ readonly id: "auction";
51
62
  readonly label: "Auctions";
52
63
  }, {
53
- readonly id: "Pre-orders";
64
+ readonly id: "pre-order";
54
65
  readonly label: "Pre-orders";
55
66
  }, {
56
- readonly id: "Prize Draws";
67
+ readonly id: "prize-draw";
57
68
  readonly label: "Prize Draws";
58
69
  }, {
59
- readonly id: "Classifieds";
70
+ readonly id: "classified";
60
71
  readonly label: "Classifieds";
61
72
  }, {
62
- readonly id: "Digital Codes";
73
+ readonly id: "digital-code";
63
74
  readonly label: "Digital Codes";
64
75
  }, {
65
- readonly id: "Live Items";
76
+ readonly id: "live";
66
77
  readonly label: "Live Items";
78
+ }, {
79
+ readonly id: "art";
80
+ readonly label: "Art";
81
+ }, {
82
+ readonly id: "stickers";
83
+ readonly label: "Stickers";
67
84
  }];
68
85
  /** Admin > Blog — post status filter chip set. */
69
86
  export declare const ADMIN_BLOG_STATUS_TABS: readonly [{
@@ -20,17 +20,30 @@ export const ADMIN_PRODUCT_STATUS_TABS = [
20
20
  { id: "draft", label: "Draft" },
21
21
  { id: "archived", label: "Archived" },
22
22
  ];
23
- /** Admin > Products — listing-type filter chip set. Legacy label-as-id IDs
24
- * preserved so the existing Sieve filter values keep matching. */
23
+ /**
24
+ * Admin > Products listing-type filter chip set.
25
+ *
26
+ * IDs are canonical `ListingType` values (2026-08-21). They used to be display
27
+ * LABELS ("Products", "Pre-orders", …) translated to a Sieve clause through a
28
+ * `TYPE_FILTER_MAP` lookup in AdminProductsView — an indirection that hid two
29
+ * whole missing types (`art`, `stickers`) and put the array outside the reach
30
+ * of `audit-filter-tab-enums.mjs`, which compares chip ids against the real
31
+ * backing enum. Now registered there, so a missing or misspelled type blocks.
32
+ *
33
+ * Multi-select: this feeds a `<FilterChipGroup multiple>`, so several types can
34
+ * be active at once and the selection is emitted as a pipe-joined OR-group.
35
+ */
25
36
  export const ADMIN_PRODUCT_LISTING_TYPE_TABS = [
26
37
  ALL_TAB,
27
- { id: "Products", label: "Products" },
28
- { id: "Auctions", label: "Auctions" },
29
- { id: "Pre-orders", label: "Pre-orders" },
30
- { id: "Prize Draws", label: "Prize Draws" },
31
- { id: "Classifieds", label: "Classifieds" },
32
- { id: "Digital Codes", label: "Digital Codes" },
33
- { id: "Live Items", label: "Live Items" },
38
+ { id: "standard", label: "Products" },
39
+ { id: "auction", label: "Auctions" },
40
+ { id: "pre-order", label: "Pre-orders" },
41
+ { id: "prize-draw", label: "Prize Draws" },
42
+ { id: "classified", label: "Classifieds" },
43
+ { id: "digital-code", label: "Digital Codes" },
44
+ { id: "live", label: "Live Items" },
45
+ { id: "art", label: "Art" },
46
+ { id: "stickers", label: "Stickers" },
34
47
  ];
35
48
  /** Admin > Blog — post status filter chip set. */
36
49
  export const ADMIN_BLOG_STATUS_TABS = [
@@ -40,11 +40,16 @@ export function useAdminListing(config) {
40
40
  }, [filterKeys]);
41
41
  const resetAll = useCallback(() => {
42
42
  const updates = { q: "", sort: "" };
43
+ // Restore each key to its CONFIGURED default, not to "". A view using
44
+ // `filterDefaults` (e.g. hide-inactive on first load) would otherwise have
45
+ // "Reset" land somewhere the user could never get back to by reloading —
46
+ // reset showed MORE rows than a fresh visit, which reads as a bug in the
47
+ // filter rather than in the reset.
43
48
  for (const k of filterKeys)
44
- updates[k] = "";
49
+ updates[k] = filterDefaults?.[k] ?? "";
45
50
  table.setMany(updates);
46
51
  setSearchInput("");
47
- }, [filterKeys, table]);
52
+ }, [filterKeys, filterDefaults, table]);
48
53
  const commitSearch = useCallback(() => {
49
54
  table.set("q", searchInput.trim());
50
55
  }, [searchInput, table]);
@@ -342,6 +342,11 @@ export interface SiteSettingsDocument extends BaseDocument {
342
342
  gstPercent: number;
343
343
  /** Per-transaction gateway minimum fee in rupees. Ensures total charge is never below this floor. 0 = no minimum. */
344
344
  minimumTransactionFee: number;
345
+ /**
346
+ * Rupee ceiling on the buyer-facing platform commission. Charged on every
347
+ * payment method (COD/UPI-manual/cash/EMI/Razorpay), once per checkout.
348
+ * Optional so pre-existing documents keep working — falls back to ₹10.
349
+ */ platformFeeMax?: number;
345
350
  /** Razorpay gateway cost % (absorbed by platform, not passed through separately). */
346
351
  gatewayFeePercent: number;
347
352
  codDepositPercent: number;
@@ -186,6 +186,7 @@ export const DEFAULT_SITE_SETTINGS_DATA = {
186
186
  platformFeePercent: 5,
187
187
  gstPercent: 18,
188
188
  minimumTransactionFee: 0,
189
+ platformFeeMax: 10,
189
190
  gatewayFeePercent: 2,
190
191
  codDepositPercent: 10,
191
192
  codHandlingFeeMin: 200,
@@ -24,12 +24,12 @@ import { increment } from "../../../contracts/field-ops";
24
24
  import { getDefaultCurrency } from "../../../core/baseline-resolver";
25
25
  import { cartRepository } from "../../cart/repository/cart.repository";
26
26
  import { ROUTES } from "../../../next/routing/route-map";
27
- import { CART_LANE } from "../../../_internal/shared/checkout/lanes";
27
+ import { CART_LANE, LANE_CHECKOUT_WINDOW_MS } from "../../../_internal/shared/checkout/lanes";
28
28
  /**
29
29
  * Buy-Now and auction settlement give the buyer the same 48h window to pay for
30
30
  * a locked line. Kept identical on purpose — both are "you now owe this".
31
31
  */
32
- const AUCTION_CHECKOUT_WINDOW_MS = 48 * 60 * 60 * 1000;
32
+ const AUCTION_CHECKOUT_WINDOW_MS = LANE_CHECKOUT_WINDOW_MS.auction;
33
33
  import { resolveDate } from "../../../utils";
34
34
  // --- Domain Functions ----------------------------------------------------------
35
35
  export async function placeBid(userId, userEmail, input) {
@@ -60,7 +60,7 @@ function AuctionRow({ auction, portal, }) {
60
60
  const sorted = useMemo(() => [...auction.bids].sort((a, b) => +new Date(b.bidDate) - +new Date(a.bidDate)), [auction.bids]);
61
61
  const highest = auction.bids.reduce((max, b) => Math.max(max, b.bidAmount), 0);
62
62
  const isWinning = auction.bids.some((b) => b.isWinning);
63
- return (_jsxs(Div, { className: `border border-[var(--appkit-color-border)] ${__O.hidden} bg-[var(--appkit-color-surface)]`, rounded: "xl", shadow: "sm", children: [_jsxs(Button, { type: "button", variant: "ghost", onClick: () => setExpanded((v) => !v), gap: "lg", paddingX: "md", paddingY: "md", className: "w-full text-left", "aria-expanded": expanded, children: [_jsx(ChevronRight, { className: `shrink-0 text-[var(--appkit-color-text-muted)] transition-transform ${expanded ? "rotate-90" : ""}`, size: 16 }), _jsx(Div, { className: "flex-1 min-w-0", children: _jsx(Text, { className: "text-[var(--appkit-color-text)] line-clamp-1", size: "sm", weight: "semibold", children: auction.productTitle }) }), _jsxs(Row, { gap: "sm", className: "shrink-0", children: [isWinning && (_jsx(Badge, { variant: "active", children: "Winning" })), _jsxs(Text, { variant: "secondary", size: "xs", children: [auction.bids.length, " bid", auction.bids.length !== 1 ? "s" : ""] }), _jsx(Text, { className: "text-[var(--appkit-color-text)]", size: "sm", weight: "semibold", children: formatBidAmount(highest) })] })] }), expanded && (_jsxs(Div, { className: "border-t border-[var(--appkit-color-border)]", children: [_jsxs(Div, { textSize: "xs", textWeight: "medium", paddingX: "x-md", paddingY: "y-xs", className: `grid text-[var(--appkit-color-text-muted)] uppercase tracking-wide border-b border-[var(--appkit-color-border-subtle)] ${portal === "buyer" ? "[grid-template-columns:1fr_auto_auto]" : "[grid-template-columns:1fr_1fr_auto_auto]"}`, children: [_jsx(Span, { children: portal === "buyer" ? "Amount" : "Bidder" }), portal !== "buyer" && _jsx(Span, { children: "Amount" }), _jsx(Span, { children: "Status" }), _jsx(Span, { className: "text-right", children: "Time" })] }), sorted.map((bid) => (_jsxs(Div, { layout: "grid", align: "center", paddingX: "x-md", paddingY: "y-xs-tall", className: `border-b border-[var(--appkit-color-border-subtle)] last:border-0 hover:bg-[var(--appkit-color-border-subtle)] transition-colors ${portal === "buyer" ? "[grid-template-columns:1fr_auto_auto]" : "[grid-template-columns:1fr_1fr_auto_auto]"}`, children: [portal !== "buyer" && (_jsx(Text, { className: "text-[var(--appkit-color-text)] truncate pr-[0.75rem]", size: "sm", children: bid.userName || bid.userId })), _jsx(Text, { className: "text-[var(--appkit-color-text)]", size: "sm", weight: "medium", children: formatBidAmount(bid.bidAmount) }), _jsx(Badge, { variant: STATUS_VARIANT[bid.status] ?? "pending", className: "capitalize", children: bid.status }), _jsxs(Stack, { gap: "xs", className: "items-end", children: [_jsx(Text, { variant: "secondary", size: "xs", align: "end", children: relDate(bid.bidDate) }), portal === "buyer" && bid.status === "won" && (_jsx(TextLink, { href: bid.orderId
63
+ return (_jsxs(Div, { className: `border border-[var(--appkit-color-border)] ${__O.hidden} bg-[var(--appkit-color-surface)]`, rounded: "xl", shadow: "sm", children: [_jsxs(Button, { type: "button", variant: "ghost", onClick: () => setExpanded((v) => !v), gap: "lg", paddingX: "md", paddingY: "md", className: "w-full text-left", "aria-expanded": expanded, children: [_jsx(ChevronRight, { className: `shrink-0 text-[var(--appkit-color-text-muted)] transition-transform ${expanded ? "rotate-90" : ""}`, size: 16 }), _jsx(Div, { className: "flex-1 min-w-0", children: _jsx(Text, { className: "text-[var(--appkit-color-text)] line-clamp-1", size: "sm", weight: "semibold", children: auction.productTitle }) }), _jsxs(Row, { gap: "sm", className: "shrink-0", children: [isWinning && (_jsx(Badge, { variant: "active", children: "Winning" })), _jsxs(Text, { variant: "secondary", size: "xs", children: [auction.bids.length, " bid", auction.bids.length !== 1 ? "s" : ""] }), _jsx(Text, { className: "text-[var(--appkit-color-text)]", size: "sm", weight: "semibold", children: formatBidAmount(highest) })] })] }), expanded && (_jsxs(Div, { className: "border-t border-[var(--appkit-color-border)]", children: [_jsxs(Div, { textSize: "xs", textWeight: "medium", paddingX: "x-md", paddingY: "y-xs", className: `grid text-[var(--appkit-color-text-muted)] uppercase tracking-wide border-b border-[var(--appkit-color-border-subtle)] ${portal === "buyer" ? "[grid-template-columns:1fr_auto_auto]" : "[grid-template-columns:1fr_1fr_auto_auto]"}`, children: [_jsx(Span, { children: portal === "buyer" ? "Amount" : "Bidder" }), portal !== "buyer" && _jsx(Span, { children: "Amount" }), _jsx(Span, { children: "Status" }), _jsx(Span, { className: "text-right", children: "Time" })] }), sorted.map((bid) => (_jsxs(Div, { layout: "grid", align: "center", paddingX: "x-md", paddingY: "y-xs-tall", className: `border-b border-[var(--appkit-color-border-subtle)] last:border-0 hover:bg-[var(--appkit-color-border-subtle)] transition-colors ${portal === "buyer" ? "[grid-template-columns:1fr_auto_auto]" : "[grid-template-columns:1fr_1fr_auto_auto]"}`, children: [portal !== "buyer" && (_jsx(Text, { className: "text-[var(--appkit-color-text)] truncate pr-[0.75rem]", size: "sm", children: bid.userName || bid.userId })), _jsx(Text, { className: "text-[var(--appkit-color-text)]", size: "sm", weight: "medium", children: formatBidAmount(bid.bidAmount) }), _jsx(Badge, { variant: STATUS_VARIANT[bid.status] ?? "pending", className: "capitalize", children: bid.status }), _jsxs(Stack, { gap: "xs", align: "end", children: [_jsx(Text, { variant: "secondary", size: "xs", align: "end", children: relDate(bid.bidDate) }), portal === "buyer" && bid.status === "won" && (_jsx(TextLink, { href: bid.orderId
64
64
  ? String(ROUTES.USER.ORDER_DETAIL(bid.orderId))
65
65
  : `${String(ROUTES.USER.CHECKOUT)}?lane=${CART_LANE.AUCTION}`, size: "xs", weight: "semibold", children: bid.orderId ? "View order" : "Pay now →" }))] })] }, bid.id)))] }))] }));
66
66
  }
@@ -31,6 +31,7 @@ export function AuctionBottomActions({ currentBid, currency, bidCount, isEnded,
31
31
  ],
32
32
  secondaryLabel: formatCountdownLabel(remaining),
33
33
  infoLabel: `${formatCurrency(currentBid, currency)} · ${bidCount} bid${bidCount !== 1 ? "s" : ""}`,
34
+ desktop: "after-scroll",
34
35
  });
35
36
  return null;
36
37
  }