@mohasinac/appkit 4.42.18 → 4.44.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 (263) hide show
  1. package/dist/_internal/client/features/lottery/LotteryDetailView.js +22 -1
  2. package/dist/_internal/server/features/categories/adapters.d.ts +47 -0
  3. package/dist/_internal/server/features/categories/adapters.js +235 -0
  4. package/dist/_internal/server/features/checkout/actions.js +161 -22
  5. package/dist/_internal/server/features/checkout/expand-order-items.js +34 -0
  6. package/dist/_internal/server/features/orders/adapters.js +21 -0
  7. package/dist/_internal/server/features/products/list-public.d.ts +24 -0
  8. package/dist/_internal/server/features/products/list-public.js +10 -2
  9. package/dist/_internal/server/features/refunds/actions.js +1 -1
  10. package/dist/_internal/server/features/seo/sitemap.js +54 -15
  11. package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
  12. package/dist/_internal/server/features/stores/adapters.js +6 -0
  13. package/dist/_internal/server/functions/scheduled.d.ts +4 -2
  14. package/dist/_internal/server/functions/scheduled.js +64 -6
  15. package/dist/_internal/server/jobs/core/adminAnalytics.d.ts +0 -1
  16. package/dist/_internal/server/jobs/core/adminAnalytics.js +9 -1
  17. package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.d.ts +47 -0
  18. package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.js +468 -0
  19. package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +14 -1
  20. package/dist/_internal/server/jobs/core/countersReconcile.js +24 -8
  21. package/dist/_internal/server/jobs/core/onCategoryWrite.js +59 -0
  22. package/dist/_internal/server/jobs/core/onProductWrite.js +31 -0
  23. package/dist/_internal/server/jobs/core/onReviewWrite.js +8 -0
  24. package/dist/_internal/server/jobs/core/onStoreWrite.d.ts +1 -1
  25. package/dist/_internal/server/jobs/core/onStoreWrite.js +8 -2
  26. package/dist/_internal/server/jobs/core/priceIndexRollup.d.ts +47 -0
  27. package/dist/_internal/server/jobs/core/priceIndexRollup.js +234 -0
  28. package/dist/_internal/server/jobs/core/revalidateNotify.d.ts +28 -0
  29. package/dist/_internal/server/jobs/core/revalidateNotify.js +125 -0
  30. package/dist/_internal/server/jobs/core/{testerSandboxCleanup.d.ts → testDataCleanup.d.ts} +3 -3
  31. package/dist/_internal/server/jobs/core/{testerSandboxCleanup.js → testDataCleanup.js} +3 -3
  32. package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +5 -1
  33. package/dist/_internal/server/jobs/handlers/analyticsAlertEvaluate.d.ts +2 -0
  34. package/dist/_internal/server/jobs/handlers/analyticsAlertEvaluate.js +2 -0
  35. package/dist/_internal/server/jobs/handlers/index.d.ts +3 -1
  36. package/dist/_internal/server/jobs/handlers/index.js +3 -1
  37. package/dist/_internal/server/jobs/handlers/messages.d.ts +5 -2
  38. package/dist/_internal/server/jobs/handlers/messages.js +5 -2
  39. package/dist/_internal/server/jobs/handlers/priceIndexRollup.d.ts +2 -0
  40. package/dist/_internal/server/jobs/handlers/priceIndexRollup.js +2 -0
  41. package/dist/_internal/server/jobs/handlers/testDataCleanup.d.ts +2 -0
  42. package/dist/_internal/server/jobs/handlers/testDataCleanup.js +2 -0
  43. package/dist/_internal/shared/actions/action-registry.js +34 -0
  44. package/dist/_internal/shared/cache/revalidate-targets.d.ts +49 -0
  45. package/dist/_internal/shared/cache/revalidate-targets.js +157 -0
  46. package/dist/_internal/shared/checkout/order-math.d.ts +113 -3
  47. package/dist/_internal/shared/checkout/order-math.js +194 -7
  48. package/dist/_internal/shared/features/categories/price-guidance.d.ts +110 -0
  49. package/dist/_internal/shared/features/categories/price-guidance.js +136 -0
  50. package/dist/_internal/shared/features/site-settings/gst-registration.d.ts +90 -0
  51. package/dist/_internal/shared/features/site-settings/gst-registration.js +126 -0
  52. package/dist/_internal/shared/fees/calculator.d.ts +105 -0
  53. package/dist/_internal/shared/fees/calculator.js +143 -0
  54. package/dist/_internal/shared/listing-types/art/config.d.ts +0 -6
  55. package/dist/_internal/shared/listing-types/classified/config.d.ts +0 -6
  56. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +0 -6
  57. package/dist/_internal/shared/listing-types/live/config.d.ts +0 -6
  58. package/dist/_internal/shared/listing-types/standard/config.d.ts +0 -6
  59. package/dist/_internal/shared/listing-types/stickers/config.d.ts +0 -6
  60. package/dist/_internal/shared/templating/placeholders.d.ts +70 -0
  61. package/dist/_internal/shared/templating/placeholders.js +73 -0
  62. package/dist/client.d.ts +2 -0
  63. package/dist/client.js +1 -0
  64. package/dist/constants/api-endpoints.d.ts +42 -0
  65. package/dist/constants/api-endpoints.js +14 -0
  66. package/dist/features/admin/actions/admin-actions.d.ts +1 -1
  67. package/dist/features/admin/actions/admin-actions.js +1 -1
  68. package/dist/features/admin/components/AdminCategoryEditorView.js +269 -3
  69. package/dist/features/admin/components/AdminProductEditorView.js +8 -1
  70. package/dist/features/admin/components/AdminProductsView.js +78 -7
  71. package/dist/features/admin/components/AdminSiteSettingsView.js +20 -1
  72. package/dist/features/admin/components/AdminTaxCodeEditorView.d.ts +10 -0
  73. package/dist/features/admin/components/AdminTaxCodeEditorView.js +160 -0
  74. package/dist/features/admin/components/AdminTaxCodesView.d.ts +13 -0
  75. package/dist/features/admin/components/AdminTaxCodesView.js +86 -0
  76. package/dist/features/admin/components/AdminTesterChecklistView.js +1 -1
  77. package/dist/features/admin/components/analytics/AdminTopProductsTable.js +1 -1
  78. package/dist/features/auctions/components/AuctionBottomActions.js +10 -1
  79. package/dist/features/auctions/components/AuctionDetailPageView.js +2 -1
  80. package/dist/features/auctions/components/AuctionLiveStatusBadge.d.ts +20 -0
  81. package/dist/features/auctions/components/AuctionLiveStatusBadge.js +21 -0
  82. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +1 -1
  83. package/dist/features/auctions/components/PlaceBidFormClient.js +20 -2
  84. package/dist/features/auctions/hooks/useIsAuctionEnded.d.ts +41 -0
  85. package/dist/features/auctions/hooks/useIsAuctionEnded.js +76 -0
  86. package/dist/features/auth/schemas/firestore.d.ts +0 -1
  87. package/dist/features/blog/api/route.js +16 -1
  88. package/dist/features/blog/schemas/firestore.d.ts +2 -2
  89. package/dist/features/catalogue/components/CatalogueItemEditorView.js +14 -3
  90. package/dist/features/categories/actions/category-actions.d.ts +2 -2
  91. package/dist/features/categories/actions/category-actions.js +22 -10
  92. package/dist/features/categories/api/[id]/description-template/route.d.ts +45 -0
  93. package/dist/features/categories/api/[id]/description-template/route.js +252 -0
  94. package/dist/features/categories/api/route.js +156 -11
  95. package/dist/features/categories/components/CategoriesIndexListing.d.ts +1 -3
  96. package/dist/features/categories/components/CategoriesIndexListing.js +1 -1
  97. package/dist/features/categories/components/CategoriesIndexPageView.d.ts +27 -1
  98. package/dist/features/categories/components/CategoriesIndexPageView.js +24 -33
  99. package/dist/features/categories/components/CategoryDetailPageView.js +43 -10
  100. package/dist/features/categories/components/CategoryTemplatePicker.d.ts +65 -0
  101. package/dist/features/categories/components/CategoryTemplatePicker.js +170 -0
  102. package/dist/features/categories/constants/listing-categories.d.ts +17 -0
  103. package/dist/features/categories/constants/listing-categories.js +53 -0
  104. package/dist/features/categories/hooks/useCategoryTree.js +28 -2
  105. package/dist/features/categories/repository/categories.repository.d.ts +73 -12
  106. package/dist/features/categories/repository/categories.repository.js +203 -42
  107. package/dist/features/categories/repository/price-index.repository.d.ts +115 -0
  108. package/dist/features/categories/repository/price-index.repository.js +85 -0
  109. package/dist/features/categories/schemas/category-content.d.ts +206 -0
  110. package/dist/features/categories/schemas/category-content.js +28 -0
  111. package/dist/features/categories/schemas/firestore.d.ts +74 -5
  112. package/dist/features/categories/schemas/firestore.js +74 -3
  113. package/dist/features/categories/schemas/index.d.ts +1047 -0
  114. package/dist/features/categories/schemas/index.js +96 -0
  115. package/dist/features/categories/server.d.ts +1 -0
  116. package/dist/features/categories/server.js +7 -0
  117. package/dist/features/categories/types/index.d.ts +9 -0
  118. package/dist/features/events/schemas/firestore.d.ts +2 -2
  119. package/dist/features/faq/schemas/firestore.d.ts +2 -1
  120. package/dist/features/faq/schemas/firestore.js +20 -8
  121. package/dist/features/faq/utils/getHomepageFaqsCached.d.ts +20 -0
  122. package/dist/features/faq/utils/getHomepageFaqsCached.js +22 -0
  123. package/dist/features/homepage/components/MarketplaceHomepageView.js +13 -3
  124. package/dist/features/homepage/lib/live-stats.js +18 -8
  125. package/dist/features/media/MediaImage.js +19 -2
  126. package/dist/features/orders/schemas/firestore.d.ts +70 -0
  127. package/dist/features/orders/types/index.d.ts +55 -0
  128. package/dist/features/products/actions/product-actions.d.ts +0 -9
  129. package/dist/features/products/actions/product-actions.js +25 -0
  130. package/dist/features/products/columns/index.d.ts +0 -1
  131. package/dist/features/products/columns/index.js +5 -1
  132. package/dist/features/products/components/ArtStickersListView.js +7 -1
  133. package/dist/features/products/components/PrizeDrawsSection.js +5 -0
  134. package/dist/features/products/components/ProductFilters.d.ts +0 -9
  135. package/dist/features/products/components/ProductForm.d.ts +27 -1
  136. package/dist/features/products/components/ProductForm.js +26 -12
  137. package/dist/features/products/components/ProductsIndexPageView.js +1 -1
  138. package/dist/features/products/constants/sieve.d.ts +0 -6
  139. package/dist/features/products/constants/sieve.js +35 -2
  140. package/dist/features/products/repository/products.repository.d.ts +115 -1
  141. package/dist/features/products/repository/products.repository.js +491 -39
  142. package/dist/features/products/schemas/firestore.d.ts +65 -5
  143. package/dist/features/products/schemas/firestore.js +9 -0
  144. package/dist/features/products/schemas/product-features.d.ts +140 -2
  145. package/dist/features/products/schemas/product-features.js +62 -1
  146. package/dist/features/promotions/repository/coupons.repository.js +6 -1
  147. package/dist/features/promotions/schemas/firestore.d.ts +44 -1
  148. package/dist/features/promotions/schemas/firestore.js +76 -2
  149. package/dist/features/reviews/repository/reviews.repository.d.ts +22 -0
  150. package/dist/features/reviews/repository/reviews.repository.js +35 -0
  151. package/dist/features/seller/actions/seller-actions.js +17 -2
  152. package/dist/features/seller/components/PrintCenterView.d.ts +3 -2
  153. package/dist/features/seller/components/PrintCenterView.js +3 -2
  154. package/dist/features/seller/components/SellerAnalyticsAlertsView.js +21 -11
  155. package/dist/features/seller/components/SellerOrdersView.js +6 -1
  156. package/dist/features/seller/components/SellerPayoutSettingsView.js +19 -3
  157. package/dist/features/seller/components/SellerProductShell.d.ts +50 -2
  158. package/dist/features/seller/components/SellerProductShell.js +101 -9
  159. package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +13 -1
  160. package/dist/features/seller/components/SellerProductsFilterDrawer.js +2 -2
  161. package/dist/features/seller/components/SellerProductsView.js +108 -3
  162. package/dist/features/seller/types/index.d.ts +17 -0
  163. package/dist/features/seller/utils/product-draft-mapping.d.ts +13 -0
  164. package/dist/features/seller/utils/product-draft-mapping.js +17 -0
  165. package/dist/features/store-extensions/constants/alert-metrics.d.ts +86 -0
  166. package/dist/features/store-extensions/constants/alert-metrics.js +142 -0
  167. package/dist/features/store-extensions/schemas/firestore.d.ts +19 -1
  168. package/dist/features/stores/schemas/firestore.d.ts +31 -2
  169. package/dist/features/tax-codes/repository/tax-codes.repository.d.ts +55 -0
  170. package/dist/features/tax-codes/repository/tax-codes.repository.js +131 -0
  171. package/dist/features/tax-codes/schemas/firestore.d.ts +125 -0
  172. package/dist/features/tax-codes/schemas/firestore.js +82 -0
  173. package/dist/features/tax-codes/schemas/tax-code-form.d.ts +63 -0
  174. package/dist/features/tax-codes/schemas/tax-code-form.js +152 -0
  175. package/dist/features/tax-codes/schemas/tax-codes.validators.d.ts +107 -0
  176. package/dist/features/tax-codes/schemas/tax-codes.validators.js +85 -0
  177. package/dist/features/tester/actions/index.d.ts +0 -1
  178. package/dist/features/tester/actions/index.js +0 -1
  179. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +1 -1
  180. package/dist/features/tester/components/AdminTesterFeedbackListView.js +1 -1
  181. package/dist/features/tester/components/index.d.ts +0 -4
  182. package/dist/features/tester/components/index.js +0 -2
  183. package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +1 -20
  184. package/dist/features/tester/repository/tester-checklist-item.repository.js +0 -52
  185. package/dist/features/tester/schemas/firestore.d.ts +0 -6
  186. package/dist/features/tester/seed-data/authored/admin__admin-detail-round-trips.js +3 -3
  187. package/dist/features/tester/seed-data/authored/admin__site-system.js +4 -5
  188. package/dist/features/tester/seed-data/authored/content-discovery__category-counts-and-rollup.js +2 -2
  189. package/dist/features/tester/seed-data/authored/index.js +0 -2
  190. package/dist/features/tester/seed-data/authored/page-wiring__reachability.js +0 -19
  191. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +10 -29
  192. package/dist/index.d.ts +21 -7
  193. package/dist/index.js +53 -7
  194. package/dist/next/routing/route-map.d.ts +2 -4
  195. package/dist/next/routing/route-map.js +1 -2
  196. package/dist/repositories/index.d.ts +1 -0
  197. package/dist/repositories/index.js +1 -0
  198. package/dist/schemas/registry.d.ts +506 -0
  199. package/dist/seed/_helpers/brand-rows.d.ts +35 -0
  200. package/dist/seed/_helpers/brand-rows.js +576 -0
  201. package/dist/seed/_helpers/category-content.d.ts +39 -0
  202. package/dist/seed/_helpers/category-content.js +634 -0
  203. package/dist/seed/_helpers/category-forest.d.ts +2 -0
  204. package/dist/seed/_helpers/category-forest.js +585 -243
  205. package/dist/seed/_helpers/category-models.d.ts +5 -0
  206. package/dist/seed/_helpers/category-models.js +1364 -0
  207. package/dist/seed/_helpers/category-tree.js +17 -1
  208. package/dist/seed/_helpers/character-features.d.ts +10 -0
  209. package/dist/seed/_helpers/character-features.js +118 -0
  210. package/dist/seed/_helpers/media.d.ts +1 -1
  211. package/dist/seed/_helpers/media.js +44 -8
  212. package/dist/seed/_helpers/search-txt-wrappers.d.ts +10 -0
  213. package/dist/seed/_helpers/search-txt-wrappers.js +12 -1
  214. package/dist/seed/categories-seed-data.js +147 -169
  215. package/dist/seed/index.d.ts +1 -0
  216. package/dist/seed/index.js +1 -0
  217. package/dist/seed/manifest.d.ts +1 -1
  218. package/dist/seed/manifest.js +5 -0
  219. package/dist/seed/orders-seed-data.js +5 -3
  220. package/dist/seed/product-features-seed-data.d.ts +1 -8
  221. package/dist/seed/product-features-seed-data.js +358 -154
  222. package/dist/seed/products-art-seed-data.js +16 -4
  223. package/dist/seed/products-auctions-seed-data.js +6 -6
  224. package/dist/seed/products-classifieds-seed-data.js +17 -7
  225. package/dist/seed/products-digital-codes-seed-data.js +12 -12
  226. package/dist/seed/products-preorders-seed-data.js +8 -8
  227. package/dist/seed/products-prize-draws-seed-data.js +25 -8
  228. package/dist/seed/products-standard-seed-data.js +10 -60
  229. package/dist/seed/products-stickers-seed-data.js +6 -6
  230. package/dist/seed/site-settings-seed-data.js +24 -0
  231. package/dist/seed/store-extensions-seed-data.js +10 -4
  232. package/dist/seed/tax-codes-seed-data.d.ts +34 -0
  233. package/dist/seed/tax-codes-seed-data.js +100 -0
  234. package/dist/seed/test-data-ttl.d.ts +29 -0
  235. package/dist/seed/test-data-ttl.js +31 -0
  236. package/dist/server-entry.d.ts +1 -1
  237. package/dist/server-entry.js +6 -1
  238. package/dist/server.d.ts +4 -1
  239. package/dist/server.js +11 -2
  240. package/dist/styles.css +40 -0
  241. package/dist/ui/components/PaginatedSelect.d.ts +14 -0
  242. package/dist/ui/components/PaginatedSelect.js +0 -0
  243. package/dist/ui/components/PaginatedSelect.style.css +40 -0
  244. package/dist/utils/media-url.d.ts +26 -0
  245. package/dist/utils/media-url.js +144 -0
  246. package/dist/utils/physical-location.d.ts +23 -0
  247. package/dist/utils/physical-location.js +52 -0
  248. package/dist/utils/search-txt-builders.d.ts +20 -0
  249. package/dist/utils/search-txt-builders.js +36 -0
  250. package/package.json +1 -1
  251. package/scripts/seed-cli.mjs +4 -1
  252. package/dist/_internal/server/jobs/handlers/testerSandboxCleanup.d.ts +0 -2
  253. package/dist/_internal/server/jobs/handlers/testerSandboxCleanup.js +0 -2
  254. package/dist/features/tester/actions/leaderboard-actions.d.ts +0 -3
  255. package/dist/features/tester/actions/leaderboard-actions.js +0 -5
  256. package/dist/features/tester/components/BugHunterLeaderboardView.d.ts +0 -23
  257. package/dist/features/tester/components/BugHunterLeaderboardView.js +0 -18
  258. package/dist/features/tester/components/TesterHubView.d.ts +0 -5
  259. package/dist/features/tester/components/TesterHubView.js +0 -110
  260. package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.d.ts +0 -2
  261. package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.js +0 -78
  262. package/dist/features/tester/seed-data/tester-ttl.d.ts +0 -4
  263. package/dist/features/tester/seed-data/tester-ttl.js +0 -6
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import { type PhysicalLocationParts } from "../../../utils/physical-location";
2
3
  interface PrintCenterStore {
3
4
  id: string;
4
5
  storeName: string;
@@ -14,7 +15,7 @@ interface PrintCenterProduct {
14
15
  listingType?: string;
15
16
  condition?: string;
16
17
  stockCount?: number;
17
- physicalLocation?: string;
18
+ physicalLocation?: PhysicalLocationParts;
18
19
  barcodeId?: string;
19
20
  }
20
21
  interface PrintCenterOrder {
@@ -29,7 +30,7 @@ interface PrintCenterOrder {
29
30
  price: number;
30
31
  barcodeId?: string;
31
32
  }[];
32
- physicalLocation?: string;
33
+ physicalLocation?: PhysicalLocationParts;
33
34
  }
34
35
  interface PrintCenterViewProps {
35
36
  store?: PrintCenterStore | null;
@@ -7,6 +7,7 @@ import { Heading, Text } from "../../../ui/components/Typography";
7
7
  import { Stack, Row } from "../../../ui/components/Layout";
8
8
  import { Button } from "../../../ui/components/Button";
9
9
  import { FieldCheckbox } from "../../../ui/forms/FieldCheckbox";
10
+ import { formatPhysicalLocation, } from "../../../utils/physical-location";
10
11
  function ProductLabel({ product }) {
11
12
  const svgRef = useRef(null);
12
13
  useEffect(() => {
@@ -29,10 +30,10 @@ function ProductLabel({ product }) {
29
30
  }
30
31
  }
31
32
  }, [product.barcodeId]);
32
- return (_jsxs(Stack, { surface: "card", padding: "sm", rounded: "sm", gap: "xs", className: "break-inside-avoid text-center w-[200px]", children: [product.barcodeId ? (_jsx("svg", { ref: svgRef })) : (_jsx(Text, { size: "xs", color: "muted", children: "No barcode" })), _jsx(Text, { size: "xs", weight: "medium", truncate: 1, children: product.name }), _jsxs(Text, { size: "xs", color: "muted", children: ["\u20B9", product.price.toLocaleString("en-IN")] }), product.physicalLocation && (_jsx(Text, { size: "xs", color: "muted", children: product.physicalLocation }))] }));
33
+ return (_jsxs(Stack, { surface: "card", padding: "sm", rounded: "sm", gap: "xs", className: "break-inside-avoid text-center w-[200px]", children: [product.barcodeId ? (_jsx("svg", { ref: svgRef })) : (_jsx(Text, { size: "xs", color: "muted", children: "No barcode" })), _jsx(Text, { size: "xs", weight: "medium", truncate: 1, children: product.name }), _jsxs(Text, { size: "xs", color: "muted", children: ["\u20B9", product.price.toLocaleString("en-IN")] }), formatPhysicalLocation(product.physicalLocation) && (_jsx(Text, { size: "xs", color: "muted", children: formatPhysicalLocation(product.physicalLocation) }))] }));
33
34
  }
34
35
  function PackingSlip({ order, brandName }) {
35
- return (_jsxs(Stack, { surface: "card", padding: "md", rounded: "sm", className: "break-inside-avoid mb-4 max-w-[480px]", children: [_jsxs(Row, { justify: "between", className: "mb-2", children: [_jsx(Heading, { level: 5, children: brandName }), _jsxs(Text, { size: "xs", color: "muted", children: ["#", order.id] })] }), _jsx(Text, { size: "xs", color: "muted", children: new Date(order.createdAt).toLocaleDateString("en-IN") }), order.buyerDisplayName && _jsx(Text, { size: "sm", weight: "medium", children: order.buyerDisplayName }), order.buyerCity && _jsx(Text, { size: "xs", color: "muted", children: order.buyerCity }), order.physicalLocation && (_jsxs(Text, { size: "xs", color: "muted", children: ["Staging: ", order.physicalLocation] })), _jsx(Stack, { border: "top", padding: "t-xs", className: "mt-3", children: order.items.map((item, i) => (_jsxs(Row, { justify: "between", padding: "y-2xs", className: "border-b last:border-b-0", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "medium", children: item.productName }), item.barcodeId && _jsx(Text, { size: "xs", color: "muted", children: item.barcodeId })] }), _jsxs(Stack, { className: "text-right", children: [_jsxs(Text, { size: "xs", children: ["\u00D7", item.quantity] }), _jsxs(Text, { size: "xs", color: "muted", children: ["\u20B9", item.price.toLocaleString("en-IN")] })] })] }, i))) })] }));
36
+ return (_jsxs(Stack, { surface: "card", padding: "md", rounded: "sm", className: "break-inside-avoid mb-4 max-w-[480px]", children: [_jsxs(Row, { justify: "between", className: "mb-2", children: [_jsx(Heading, { level: 5, children: brandName }), _jsxs(Text, { size: "xs", color: "muted", children: ["#", order.id] })] }), _jsx(Text, { size: "xs", color: "muted", children: new Date(order.createdAt).toLocaleDateString("en-IN") }), order.buyerDisplayName && _jsx(Text, { size: "sm", weight: "medium", children: order.buyerDisplayName }), order.buyerCity && _jsx(Text, { size: "xs", color: "muted", children: order.buyerCity }), formatPhysicalLocation(order.physicalLocation) && (_jsxs(Text, { size: "xs", color: "muted", children: ["Staging: ", formatPhysicalLocation(order.physicalLocation)] })), _jsx(Stack, { border: "top", padding: "t-xs", className: "mt-3", children: order.items.map((item, i) => (_jsxs(Row, { justify: "between", padding: "y-2xs", className: "border-b last:border-b-0", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "medium", children: item.productName }), item.barcodeId && _jsx(Text, { size: "xs", color: "muted", children: item.barcodeId })] }), _jsxs(Stack, { className: "text-right", children: [_jsxs(Text, { size: "xs", children: ["\u00D7", item.quantity] }), _jsxs(Text, { size: "xs", color: "muted", children: ["\u20B9", item.price.toLocaleString("en-IN")] })] })] }, i))) })] }));
36
37
  }
37
38
  export function PrintCenterView({ store, brandName = "LetItRip", initialProducts = [], initialOrders = [], }) {
38
39
  const [tab, setTab] = useState("labels");
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useApiMutation } from "@mohasinac/appkit/client";
4
4
  import React, { useState, useCallback } from "react";
5
+ import { alertMetricsForScope } from "../../store-extensions/constants/alert-metrics";
5
6
  import { useQuery, useQueryClient } from "@tanstack/react-query";
6
7
  import { Alert, Badge, Button, Div, Heading, Row, Section, Stack, Text, Toggle, useToast } from "../../../ui";
7
8
  import { apiClient } from "../../../http";
@@ -39,15 +40,24 @@ const OPERATOR_OPTIONS = ANALYTICS_ALERT_OPERATORS.map((value) => ({
39
40
  value,
40
41
  label: OPERATOR_LABELS[value] ?? value,
41
42
  }));
42
- const METRIC_OPTIONS = [
43
- { value: "daily_revenue", label: "Daily Revenue (₹)" },
44
- { value: "daily_orders", label: "Daily Orders" },
45
- { value: "low_stock", label: "Low Stock (products)" },
46
- { value: "cancelled_orders", label: "Cancelled Orders" },
47
- { value: "pending_payouts", label: "Pending Payouts (₹)" },
48
- { value: "avg_rating", label: "Average Rating" },
49
- { value: "reviews_count", label: "New Reviews" },
50
- ];
43
+ /*
44
+ * 🛑 DERIVED, never a hand-written list — that list WAS the defect.
45
+ *
46
+ * These seven values (daily_revenue, daily_orders, low_stock,
47
+ * cancelled_orders, pending_payouts, avg_rating, reviews_count) shared ZERO
48
+ * tokens with the four the seed wrote, and nothing validated either because
49
+ * `metric` was `string`. It stayed invisible because no evaluator read the
50
+ * field at all, so an alert a seller configured could never fire.
51
+ *
52
+ * Now from `alertMetricsForScope("seller")`, which the evaluator switches
53
+ * over exhaustively — so a metric the job cannot compute is not offerable.
54
+ */
55
+ const METRIC_OPTIONS = alertMetricsForScope("seller").map(({ value, def }) => ({
56
+ value,
57
+ label: def.label,
58
+ }));
59
+ /** Helper text per metric, so "Orders in the window" says what it counts. */
60
+ const METRIC_DESCRIPTIONS = Object.fromEntries(alertMetricsForScope("seller").map(({ value, def }) => [value, def.describes]));
51
61
  const WINDOW_OPTIONS = [
52
62
  { value: "1", label: "Last 1 hour" },
53
63
  { value: "6", label: "Last 6 hours" },
@@ -74,11 +84,11 @@ function AlertCard({ alert, onToggle, onDelete, }) {
74
84
  const metricLabel = METRIC_OPTIONS.find((m) => m.value === alert.metric)?.label ?? alert.metric;
75
85
  const opLabel = OPERATOR_OPTIONS.find((o) => o.value === alert.operator)?.label ?? alert.operator;
76
86
  const windowLabel = WINDOW_OPTIONS.find((w) => w.value === String(alert.windowHours))?.label ?? `${alert.windowHours}h`;
77
- return (_jsxs(Row, { surface: "card", padding: "sm", align: "start", justify: "between", gap: "md", children: [_jsxs(Div, { className: "flex-1 min-w-0", children: [_jsxs(Row, { className: "mb-1", gap: "sm", wrap: true, children: [_jsx(Text, { size: "sm", weight: "medium", color: "primary", children: alert.label }), _jsx(Badge, { variant: alert.isActive ? "success" : "default", children: alert.isActive ? "Active" : "Paused" })] }), _jsxs(Text, { className: "mb-1", color: "muted", size: "xs", children: [metricLabel, " ", opLabel, " ", alert.threshold, " \u00B7 ", windowLabel] }), _jsx(Row, { gap: "xs", wrap: true, children: alert.notifyChannels.map((ch) => (_jsx(Badge, { variant: OPERATOR_BADGE[alert.operator] ?? "default", children: ch }, ch))) }), alert.lastTriggeredAt && (_jsxs(Text, { className: "mt-1", color: "faint", size: "xs", children: ["Last triggered: ", new Date(alert.lastTriggeredAt).toLocaleString("en-IN")] }))] }), _jsxs(Row, { className: "shrink-0", align: "center", gap: "sm", children: [_jsx(Toggle, { checked: alert.isActive, onChange: (v) => onToggle(alert.id, v), size: "sm" }), _jsx(Button, { variant: "ghost", size: "sm", action: ACTIONS.STORE["delete-analytics-alert"], onClick: () => onDelete(alert.id), className: "text-error hover:text-error" })] })] }));
87
+ return (_jsxs(Row, { surface: "card", padding: "sm", align: "start", justify: "between", gap: "md", children: [_jsxs(Div, { className: "flex-1 min-w-0", children: [_jsxs(Row, { className: "mb-1", gap: "sm", wrap: true, children: [_jsx(Text, { size: "sm", weight: "medium", color: "primary", children: alert.label }), _jsx(Badge, { variant: alert.isActive ? "success" : "default", children: alert.isActive ? "Active" : "Paused" })] }), _jsxs(Text, { className: "mb-1", color: "muted", size: "xs", children: [metricLabel, " ", opLabel, " ", alert.threshold, " \u00B7 ", windowLabel] }), METRIC_DESCRIPTIONS[alert.metric] ? (_jsx(Text, { className: "mb-1", color: "muted", size: "xs", children: METRIC_DESCRIPTIONS[alert.metric] })) : null, alert.lastTriggeredAt ? (_jsxs(Text, { className: "mb-1", color: "muted", size: "xs", children: ["Last fired ", new Date(alert.lastTriggeredAt).toLocaleString(), alert.lastObservedValue != null ? ` at ${alert.lastObservedValue}` : ""] })) : null, _jsx(Row, { gap: "xs", wrap: true, children: alert.notifyChannels.map((ch) => (_jsx(Badge, { variant: OPERATOR_BADGE[alert.operator] ?? "default", children: ch }, ch))) }), alert.lastTriggeredAt && (_jsxs(Text, { className: "mt-1", color: "faint", size: "xs", children: ["Last triggered: ", new Date(alert.lastTriggeredAt).toLocaleString("en-IN")] }))] }), _jsxs(Row, { className: "shrink-0", align: "center", gap: "sm", children: [_jsx(Toggle, { checked: alert.isActive, onChange: (v) => onToggle(alert.id, v), size: "sm" }), _jsx(Button, { variant: "ghost", size: "sm", action: ACTIONS.STORE["delete-analytics-alert"], onClick: () => onDelete(alert.id), className: "text-error hover:text-error" })] })] }));
78
88
  }
79
89
  const EMPTY_DRAFT = {
80
90
  label: "",
81
- metric: "daily_orders",
91
+ metric: "orders_count",
82
92
  operator: ">",
83
93
  threshold: "",
84
94
  windowHours: "24",
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { formatPhysicalLocation } from "../../../utils/physical-location";
3
4
  import { OrderAddonBadges } from "../../orders/components/OrderAddonBadges";
4
5
  import { normalizeError } from "../../../errors/normalize";
5
6
  import { Row, SIEVE_OP, sieveFilter } from "@mohasinac/appkit/client";
@@ -362,7 +363,11 @@ export function SellerOrdersView({ orderDetailApiBase = SELLER_ENDPOINTS.ORDERS,
362
363
  key: "physicalLocation",
363
364
  header: "Staging",
364
365
  className: "w-28",
365
- render: (row) => row.physicalLocation ? (_jsxs(Span, { size: "xs", className: "font-mono", color: "muted", children: [row.physicalLocation.zone, "/", row.physicalLocation.shelf, "/", row.physicalLocation.bin] })) : (_jsx(Span, { size: "xs", color: "muted", children: "\u2014" })),
366
+ render: (row) => {
367
+ // Shared formatter — see utils/physical-location.ts.
368
+ const loc = formatPhysicalLocation(row.physicalLocation);
369
+ return loc ? (_jsx(Span, { size: "xs", className: "font-mono", color: "muted", children: loc })) : (_jsx(Span, { size: "xs", color: "muted", children: "\u2014" }));
370
+ },
366
371
  },
367
372
  {
368
373
  key: "updatedAt",
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useEffect, useMemo, useState } from "react";
4
4
  import { z } from "zod";
5
- import { Alert, Badge, Checkbox, Div, FormField, FormGroup, Heading, Label, Row, Stack, Text, Toggle } from "../../../ui";
5
+ import { Alert, Badge, Checkbox, Div, FormField, FormGroup, Heading, Label, Row, Span, Stack, Text, Toggle } from "../../../ui";
6
6
  import { StackedViewShell } from "../../../ui";
7
7
  import { FormShellContext, useFormShellState, applyZodIssues, FormErrorSummary } from "../../../ui/forms";
8
8
  import { SectionForm, useSectionFormNav, annotate } from "../../shell";
@@ -54,6 +54,17 @@ const payoutSettingsDraftSchema = z.object({
54
54
  }),
55
55
  minimumThreshold: annotate(z.string(), { section: "preferences", order: 2, row: "pair" }),
56
56
  emiEnabled: annotate(z.boolean(), { section: "preferences", order: 3, row: "quarter" }),
57
+ /*
58
+ * How the seller types prices. Lives in PAYOUT settings, not the store
59
+ * profile, because payout is what it actually changes: at 5% a ₹999
60
+ * inclusive listing pays out ₹951.43, while the same number typed ex-tax is
61
+ * stored as ₹1,048.95 and pays out ₹999.00.
62
+ *
63
+ * 🛑 It does NOT re-price existing listings — the gross-up happens at write
64
+ * time — so the label has to say so, or a seller flips it and assumes their
65
+ * catalogue moved.
66
+ */
67
+ pricesIncludeGst: annotate(z.boolean(), { section: "preferences", order: 4, row: "quarter" }),
57
68
  }).superRefine((v, ctx) => {
58
69
  if (v.method === "upi") {
59
70
  if (!v.upiId.trim()) {
@@ -89,6 +100,8 @@ const DEFAULT_DRAFT = {
89
100
  autoPayout: true,
90
101
  minimumThreshold: "",
91
102
  emiEnabled: false,
103
+ // Inclusive is the documented default — see StoreDocument.pricesIncludeGst.
104
+ pricesIncludeGst: true,
92
105
  };
93
106
  export function SellerPayoutSettingsView({ apiBase = SELLER_ENDPOINTS.PAYOUT_SETTINGS }) {
94
107
  const [draft, setDraft] = useState(DEFAULT_DRAFT);
@@ -119,6 +132,8 @@ export function SellerPayoutSettingsView({ apiBase = SELLER_ENDPOINTS.PAYOUT_SET
119
132
  ? String(res.data.preferences.minimumThreshold)
120
133
  : "",
121
134
  emiEnabled: res?.data?.emiEnabled ?? false,
135
+ // Absent means inclusive, matching the schema default exactly.
136
+ pricesIncludeGst: res?.data?.pricesIncludeGst ?? true,
122
137
  });
123
138
  })
124
139
  .catch(console.error)
@@ -146,6 +161,7 @@ export function SellerPayoutSettingsView({ apiBase = SELLER_ENDPOINTS.PAYOUT_SET
146
161
  const body = {
147
162
  ...methodFields,
148
163
  emiEnabled: draft.emiEnabled,
164
+ pricesIncludeGst: draft.pricesIncludeGst,
149
165
  taxInfo: {
150
166
  gstin: draft.gstin.trim(),
151
167
  pan: draft.pan.trim(),
@@ -212,8 +228,8 @@ export function SellerPayoutSettingsView({ apiBase = SELLER_ENDPOINTS.PAYOUT_SET
212
228
  {
213
229
  id: "preferences",
214
230
  label: "Preferences",
215
- fields: ["autoPayout", "minimumThreshold", "emiEnabled"],
216
- render: ({ values, onChange }) => (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Payout Preferences" }), _jsx(Toggle, { checked: values.autoPayout, onChange: (checked) => onChange({ autoPayout: checked }), label: "Enable auto-payout \u2014 automatically transfer earnings on schedule", disabled: busy }), _jsx(FormField, { name: "minimumThreshold", label: "Minimum Payout Threshold (\u20B9)", type: "number", value: values.minimumThreshold, onChange: (v) => onChange({ minimumThreshold: v }), placeholder: "e.g. 500", helpText: "Payouts are held until your balance exceeds this amount. Leave blank for no minimum.", disabled: busy }), _jsxs(Div, { className: `${__P.p3} bg-[var(--appkit-color-surface-alt)] border border-[var(--appkit-color-border)]`, rounded: "lg", children: [_jsx(Text, { className: "mb-1", size: "sm", weight: "medium", children: "Payout Schedule" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Auto-payouts run every Monday for the previous week's settled orders. Manual payouts can be requested from the Payouts page at any time." })] }), _jsx(Toggle, { checked: values.emiEnabled, onChange: (checked) => onChange({ emiEnabled: checked }), label: "Offer EMI financing on eligible orders", disabled: busy }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Only shown to buyers when platform-wide EMI is also enabled and their cart subtotal from your store exceeds the site's minimum order value. Orders on an EMI plan hold your payout until every installment is paid, unless you mark the item \"ship before EMI completes\"." })] })),
231
+ fields: ["autoPayout", "minimumThreshold", "emiEnabled", "pricesIncludeGst"],
232
+ render: ({ values, onChange }) => (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Payout Preferences" }), _jsx(Toggle, { checked: values.autoPayout, onChange: (checked) => onChange({ autoPayout: checked }), label: "Enable auto-payout \u2014 automatically transfer earnings on schedule", disabled: busy }), _jsx(FormField, { name: "minimumThreshold", label: "Minimum Payout Threshold (\u20B9)", type: "number", value: values.minimumThreshold, onChange: (v) => onChange({ minimumThreshold: v }), placeholder: "e.g. 500", helpText: "Payouts are held until your balance exceeds this amount. Leave blank for no minimum.", disabled: busy }), _jsxs(Div, { className: `${__P.p3} bg-[var(--appkit-color-surface-alt)] border border-[var(--appkit-color-border)]`, rounded: "lg", children: [_jsx(Text, { className: "mb-1", size: "sm", weight: "medium", children: "Payout Schedule" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Auto-payouts run every Monday for the previous week's settled orders. Manual payouts can be requested from the Payouts page at any time." })] }), _jsx(Toggle, { checked: values.emiEnabled, onChange: (checked) => onChange({ emiEnabled: checked }), label: "Offer EMI financing on eligible orders", disabled: busy }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Only shown to buyers when platform-wide EMI is also enabled and their cart subtotal from your store exceeds the site's minimum order value. Orders on an EMI plan hold your payout until every installment is paid, unless you mark the item \"ship before EMI completes\"." }), _jsx(Toggle, { checked: values.pricesIncludeGst, onChange: (checked) => onChange({ pricesIncludeGst: checked }), label: "The prices I enter already include GST", disabled: busy }), _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: ["Buyers always see a tax-inclusive price, so this only changes how your own entry is read. Leave it", " ", _jsx(Span, { weight: "semibold", children: "on" }), " and a \u20B9999 listing at 5% GST means the buyer pays \u20B9999 and you earn \u20B9951.43 \u2014 the \u20B947.57 is GST we collect and remit for you. Turn it", " ", _jsx(Span, { weight: "semibold", children: "off" }), " and \u20B9999 is treated as your ex-GST price: the listing is shown at \u20B91,048.95 and you earn \u20B9999."] }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Changing this does not re-price listings you have already published. It applies to products you save from now on." })] })),
217
233
  },
218
234
  ], [busy, current]);
219
235
  const { openIds, setOpenIds, goToSection, fieldToSectionIndex, sectionMeta } = useSectionFormNav(sections, draft);
@@ -9,6 +9,35 @@ export interface SellerProductDraft {
9
9
  brand?: string;
10
10
  condition?: string;
11
11
  tags?: string[];
12
+ authenticity?: "original" | "reproduction" | "unverified";
13
+ specifications?: {
14
+ name: string;
15
+ value: string;
16
+ unit?: string;
17
+ }[];
18
+ /** Feature IDS, never prose — `array-contains` is what faceting runs on. */
19
+ features?: string[];
20
+ /**
21
+ * 🛑 OPTIONAL, on purpose, and the reason is C5a.
22
+ *
23
+ * The figure a carrier bills on is the PACKED PARCEL, not the item: a bey is
24
+ * ~30 g and ~300 g boxed. A required product weight would therefore be an
25
+ * item weight that systematically under-estimates every booking — and since
26
+ * the booking is capped at the amount collected from the buyer, systematic
27
+ * under-estimation means silently absorbing the difference.
28
+ *
29
+ * So the category's PACKED default drives the checkout estimate, this field
30
+ * refines it when a seller bothers, and the real parcel figure is entered at
31
+ * shipment time, when the seller has the box in front of them.
32
+ */
33
+ weightG?: number;
34
+ dimensionsCm?: {
35
+ l: number;
36
+ w: number;
37
+ h: number;
38
+ };
39
+ appliedTemplateId?: string;
40
+ appliedTemplateBody?: string;
12
41
  mainImage?: string;
13
42
  images?: string[];
14
43
  video?: MediaField;
@@ -121,10 +150,29 @@ export interface SellerProductShellProps {
121
150
  onChange: (v: string) => void;
122
151
  }) => React.ReactNode;
123
152
  /**
124
- * Render a template selector at the top of the Basic step.
125
- * Receives a callback to apply the selected template to the draft.
153
+ * Render the category description-template picker, between the
154
+ * Category/Condition row and the Description field.
155
+ *
156
+ * 🛑 The slot existed with ZERO consumers and a single `onApply` prop, which
157
+ * was not enough to build a picker with: it could apply a body but could not
158
+ * know WHICH body to fetch, or whether applying one would destroy something
159
+ * the seller had written. C2b widens it rather than adding a second slot.
160
+ *
161
+ * `isDescriptionDirty` is the one that matters. It is the clobber rule in a
162
+ * boolean: true when the description is non-empty after trim AND not
163
+ * byte-identical to a body applied this session. The picker must not
164
+ * overwrite when it is true — it offers a "Replace description" action
165
+ * instead, which is destructive and therefore carries a confirmation.
126
166
  */
127
167
  renderTemplateSelector?: (props: {
168
+ /** The leaf the seller has chosen. Empty until they pick one. */
169
+ categoryId: string;
170
+ /** Drives which `variant` is fetched — new_in_box vs pre_owned. */
171
+ condition: string;
172
+ /** Resolved `{{slot}}` values the picker fills the body with. */
173
+ context: Record<string, string>;
174
+ /** See above. The picker must not silently overwrite when true. */
175
+ isDescriptionDirty: boolean;
128
176
  onApply: (partial: Partial<SellerProductDraft>) => void;
129
177
  }) => React.ReactNode;
130
178
  /** Called with current draft when user clicks "Save as Template". */
@@ -14,6 +14,7 @@ import { pluginFor } from "../../../_internal/shared/listing-types/_registry";
14
14
  import { DEFAULT_MIN_OFFER_PERCENT } from "../../../_internal/shared/features/offers/config";
15
15
  import { PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "../../../_internal/shared/media/limits";
16
16
  import { isFinalSale } from "../../products/constants/final-sale";
17
+ import { ProductFeaturesSelector } from "../../products/components/ProductFeaturesSelector";
17
18
  import { normalizeError } from "../../../errors/normalize";
18
19
  /**
19
20
  * "bundle" is a categoryType, not a ListingType, so it can't live in
@@ -76,7 +77,74 @@ function fromRupeesString(rupeeStr) {
76
77
  }
77
78
  // ── Step 1: Basic ─────────────────────────────────────────────────────────────
78
79
  function StepBasic({ values, onChange, renderCategorySelector, renderBrandSelector, renderTemplateSelector, }) {
79
- return (_jsxs(Stack, { gap: "md", children: [renderTemplateSelector?.({ onApply: onChange }), _jsx(FormField, { name: "title", label: "Title", type: "text", value: values.title ?? "", onChange: (v) => onChange({ title: v }), placeholder: "e.g. Charizard Base Set PSA 9" }), _jsx(FormField, { name: "description", label: "Description", type: "textarea", value: values.description ?? "", onChange: (v) => onChange({ description: v }), placeholder: "Describe your listing in detail\u2026" }), _jsxs(FormGroup, { columns: 2, children: [renderCategorySelector ? (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] mb-1", size: "sm", weight: "medium", children: "Category" }), renderCategorySelector({ value: values.category ?? "", onChange: (v) => onChange({ category: v }) })] })) : (_jsx(FormField, { name: "category", label: "Category", type: "text", value: values.category ?? "", onChange: (v) => onChange({ category: v }), placeholder: "e.g. Trading Cards" })), _jsx(FormField, { name: "condition", label: "Condition", type: "select", value: values.condition ?? "new", onChange: (v) => onChange({ condition: v }), options: CONDITION_OPTIONS })] }), renderBrandSelector ? (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] mb-1", size: "sm", weight: "medium", children: "Brand" }), renderBrandSelector({ value: values.brand ?? "", onChange: (v) => onChange({ brand: v }) })] })) : (_jsx(FormField, { name: "brand", label: "Brand", type: "text", value: values.brand ?? "", onChange: (v) => onChange({ brand: v }), placeholder: "e.g. Pok\u00E9mon Company" })), _jsx(TagInput, { label: "Tags", value: values.tags ?? [], onChange: (tags) => onChange({ tags }), placeholder: "pokemon, psa9, charizard, holo" }), _jsx(BarcodeField, { value: values.barcodeId ?? "", onChange: (v) => onChange({ barcodeId: v || undefined }), onScan: (v) => onChange({ barcodeId: v }), helperText: "Leave blank to auto-generate. Scan a pre-printed sticker to link it." })] }));
80
+ /*
81
+ * 🛑 The clobber rule, in one expression.
82
+ *
83
+ * Dirty means: non-empty after trim, AND not byte-identical to a body this
84
+ * session applied. A seller who has typed anything owns the field; a seller
85
+ * looking at a body we filled in does not, so a category change may refresh
86
+ * it silently.
87
+ *
88
+ * Byte-identical rather than "did they focus the box": a seller can click
89
+ * into the description, change nothing, and click away, and that must not
90
+ * count as authorship.
91
+ */
92
+ const description = values.description ?? "";
93
+ const isDescriptionDirty = description.trim() !== "" && description !== (values.appliedTemplateBody ?? "");
94
+ /*
95
+ * The `{{slot}}` values the picker interpolates. Only what the draft
96
+ * genuinely knows — an unresolvable slot becomes the empty string and the
97
+ * picker reports it, which is `interpolate`'s contract. Never a literal
98
+ * `{{series}}` in a published description.
99
+ *
100
+ * `condition` resolves to the LABEL, not the raw enum: a body reading
101
+ * "Condition: like_new" is a template leaking its storage format.
102
+ */
103
+ const templateContext = {
104
+ title: values.title ?? "",
105
+ brand: values.brand ?? "",
106
+ condition: CONDITION_OPTIONS.find((o) => o.value === (values.condition ?? "new"))?.label ?? "",
107
+ siteName: "LetItRip",
108
+ };
109
+ return (_jsxs(Stack, { gap: "md", children: [_jsx(FormField, { name: "title", label: "Title", type: "text", value: values.title ?? "", onChange: (v) => onChange({ title: v }), placeholder: "e.g. Charizard Base Set PSA 9" }), _jsxs(FormGroup, { columns: 2, children: [renderCategorySelector ? (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] mb-1", size: "sm", weight: "medium", children: "Category" }), renderCategorySelector({ value: values.category ?? "", onChange: (v) => onChange({ category: v }) })] })) : (_jsx(FormField, { name: "category", label: "Category", type: "text", value: values.category ?? "", onChange: (v) => onChange({ category: v }), placeholder: "e.g. Trading Cards" })), _jsx(FormField, { name: "condition", label: "Condition", type: "select", value: values.condition ?? "new", onChange: (v) => onChange({ condition: v }), options: CONDITION_OPTIONS })] }), renderTemplateSelector?.({
110
+ categoryId: values.category ?? "",
111
+ condition: values.condition ?? "new",
112
+ context: templateContext,
113
+ isDescriptionDirty,
114
+ onApply: onChange,
115
+ }), _jsx(FormField, { name: "description", label: "Description", type: "textarea", value: description, onChange: (v) => onChange({ description: v }), placeholder: "Describe your listing in detail\u2026" }), renderBrandSelector ? (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] mb-1", size: "sm", weight: "medium", children: "Brand" }), renderBrandSelector({ value: values.brand ?? "", onChange: (v) => onChange({ brand: v }) })] })) : (_jsx(FormField, { name: "brand", label: "Brand", type: "text", value: values.brand ?? "", onChange: (v) => onChange({ brand: v }), placeholder: "e.g. Pok\u00E9mon Company" })), _jsx(TagInput, { label: "Tags", value: values.tags ?? [], onChange: (tags) => onChange({ tags }), placeholder: "pokemon, psa9, charizard, holo" }), _jsx(BarcodeField, { value: values.barcodeId ?? "", onChange: (v) => onChange({ barcodeId: v || undefined }), onScan: (v) => onChange({ barcodeId: v }), helperText: "Leave blank to auto-generate. Scan a pre-printed sticker to link it." })] }));
116
+ }
117
+ // ── Step 1b: Details — the inherited-but-editable fields (C2c) ───────────────
118
+ const AUTHENTICITY_OPTIONS = [
119
+ { value: "original", label: "Original — licensed manufacture (Takara, Takara Tomy, Hasbro…)" },
120
+ { value: "reproduction", label: "Reproduction / midfake — a copy with no manufacturer's marque" },
121
+ { value: "unverified", label: "Unverified — I cannot confirm which it is" },
122
+ ];
123
+ /**
124
+ * 🛑 Three options, and there is deliberately no fourth.
125
+ *
126
+ * A poor-quality counterfeit — brittle plastic, cadmium or lead in the metal
127
+ * parts — is not a listing state this marketplace offers. It is a scam report.
128
+ * Offering "fake" as a dropdown value would make selling one a supported
129
+ * workflow.
130
+ *
131
+ * `unverified` exists so a seller who genuinely does not know has an honest
132
+ * answer available. Without it the only way to list is to claim something they
133
+ * cannot stand behind, and the field stops meaning anything.
134
+ */
135
+ function StepDetails({ values, onChange, storeId, listingType, }) {
136
+ const specs = values.specifications ?? [];
137
+ const setSpecs = (next) => onChange({ specifications: next });
138
+ const dims = values.dimensionsCm;
139
+ const setDim = (key, raw) => {
140
+ const n = Number(raw);
141
+ const base = dims ?? { l: 0, w: 0, h: 0 };
142
+ const next = { ...base, [key]: Number.isFinite(n) ? n : 0 };
143
+ // All three or none: a box with a length and no width is not a dimension,
144
+ // and the shipping estimate reads all three or ignores the field.
145
+ onChange({ dimensionsCm: next.l > 0 && next.w > 0 && next.h > 0 ? next : undefined });
146
+ };
147
+ return (_jsxs(Stack, { gap: "md", children: [_jsx(FormField, { name: "authenticity", label: "Authenticity", type: "select", required: true, value: values.authenticity ?? "", onChange: (v) => onChange({ authenticity: v }), options: [{ value: "", label: "Choose one…" }, ...AUTHENTICITY_OPTIONS] }), _jsx(Text, { size: "xs", color: "muted", children: "Shown on the listing whenever it is not Original. A reproduction is a legitimate thing to sell here as long as it is labelled \u2014 what is not allowed is selling one as the real thing. If you believe an item is a cheap counterfeit, report it rather than listing it." }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Features" }), _jsx(Text, { size: "xs", color: "muted", children: "Condition, authenticity, release type, battle type and spin direction. Some are filled in from the category when you save \u2014 remove any that do not apply and your choice sticks." }), _jsx(ProductFeaturesSelector, { value: values.features ?? [], onChange: (next) => onChange({ features: next }), productType: listingType === "bundle" ? "all" : listingType, ...(storeId ? { storeId } : {}) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Specifications" }), _jsx(Text, { size: "xs", color: "muted", children: "Left blank, the category fills these in. Anything you enter here is kept." }), specs.map((row, i) => (_jsxs(FormGroup, { columns: 3, children: [_jsx(FormField, { name: `spec-name-${i}`, label: "Name", type: "text", value: row.name, onChange: (v) => setSpecs(specs.map((r, j) => (j === i ? { ...r, name: v } : r))) }), _jsx(FormField, { name: `spec-value-${i}`, label: "Value", type: "text", value: row.value, onChange: (v) => setSpecs(specs.map((r, j) => (j === i ? { ...r, value: v } : r))) }), _jsxs(Row, { gap: "sm", align: "end", children: [_jsx(FormField, { name: `spec-unit-${i}`, label: "Unit", type: "text", value: row.unit ?? "", onChange: (v) => setSpecs(specs.map((r, j) => (j === i ? { ...r, unit: v } : r))) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setSpecs(specs.filter((_, j) => j !== i)), children: "Remove" })] })] }, i))), _jsx(Button, { variant: "secondary", size: "sm", onClick: () => setSpecs([...specs, { name: "", value: "" }]), children: "Add a specification" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Weight and size (optional)" }), _jsx(Text, { size: "xs", color: "muted", children: "\uD83D\uDED1 Leave these blank unless you know them \u2014 the category's own packed-parcel figures are used for the shipping estimate, and you enter the real weight of the box when you book the shipment. A figure here should be the PACKED weight, not the bare item." }), _jsxs(FormGroup, { columns: 4, children: [_jsx(FormField, { name: "weightG", label: "Packed weight (g)", type: "number", value: values.weightG != null ? String(values.weightG) : "", onChange: (v) => onChange({ weightG: v.trim() === "" ? undefined : Math.max(0, Number(v) || 0) }) }), _jsx(FormField, { name: "dimL", label: "Length (cm)", type: "number", value: dims?.l ? String(dims.l) : "", onChange: (v) => setDim("l", v) }), _jsx(FormField, { name: "dimW", label: "Width (cm)", type: "number", value: dims?.w ? String(dims.w) : "", onChange: (v) => setDim("w", v) }), _jsx(FormField, { name: "dimH", label: "Height (cm)", type: "number", value: dims?.h ? String(dims.h) : "", onChange: (v) => setDim("h", v) })] })] })] }));
80
148
  }
81
149
  // ── Step 2: Media ─────────────────────────────────────────────────────────────
82
150
  function StepMedia({ values, onChange, storeSlug = "store", }) {
@@ -306,13 +374,7 @@ function StepShipping({ values, onChange, renderAddressSelector, }) {
306
374
  ] }), renderAddressSelector ? (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] mb-1", size: "sm", weight: "medium", children: "Pickup Address (optional)" }), renderAddressSelector({
307
375
  value: values.pickupAddressId ?? "",
308
376
  onChange: (v) => onChange({ pickupAddressId: v }),
309
- })] })) : (_jsx(StoreAddressSelectorCreate, { label: "Pickup Address (optional)", value: values.pickupAddressId ?? "", onChange: (id) => onChange({ pickupAddressId: id }) })), _jsx(Toggle, { checked: !!values.insurance, onChange: (checked) => onChange({ insurance: checked, insuranceCost: checked ? values.insuranceCost ?? 0 : undefined }), label: "Offer shipping insurance" }), values.insurance && (_jsx(FormField, { name: "insuranceCost", label: "Insurance Cost (\u20B9)", type: "number", value: toRupeesString(values.insuranceCost), onChange: (v) => onChange({ insuranceCost: fromRupeesString(v) }), placeholder: "0" })), _jsx(FormField, { name: "gstRate", label: "GST Rate", type: "select", value: String(values.gstRate ?? 0), onChange: (v) => onChange({ gstRate: Number(v) }), options: [
310
- { value: "0", label: "Exempt (0%)" },
311
- { value: "5", label: "5%" },
312
- { value: "12", label: "12%" },
313
- { value: "18", label: "18%" },
314
- { value: "28", label: "28%" },
315
- ] }), _jsx(FormField, { name: "hsnCode", label: "HSN Code", value: values.hsnCode ?? "", onChange: (v) => onChange({ hsnCode: v }), placeholder: "e.g. 9503" })] }));
377
+ })] })) : (_jsx(StoreAddressSelectorCreate, { label: "Pickup Address (optional)", value: values.pickupAddressId ?? "", onChange: (id) => onChange({ pickupAddressId: id }) })), _jsx(Toggle, { checked: !!values.insurance, onChange: (checked) => onChange({ insurance: checked, insuranceCost: checked ? values.insuranceCost ?? 0 : undefined }), label: "Offer shipping insurance" }), values.insurance && (_jsx(FormField, { name: "insuranceCost", label: "Insurance Cost (\u20B9)", type: "number", value: toRupeesString(values.insuranceCost), onChange: (v) => onChange({ insuranceCost: fromRupeesString(v) }), placeholder: "0" }))] }));
316
378
  }
317
379
  // ── Step: Returns ─────────────────────────────────────────────────────────
318
380
  /**
@@ -516,13 +578,43 @@ productId, onSave, onPublish, onDiscard, isLoading = false, storeSlug, renderCat
516
578
  render: ({ values, onChange }) => (_jsx(StepMedia, { values: values, onChange: onChange, storeSlug: storeSlug })),
517
579
  schema: sectionSchemas.media,
518
580
  },
581
+ /*
582
+ * 🛑 Details sits between Basic and the type-specific step, not at the end.
583
+ *
584
+ * `authenticity` is required, and a required field buried after the
585
+ * optional ones is a field sellers reach by being bounced back to it from
586
+ * a failed submit. It also reads in the right order: what it IS, then what
587
+ * it is LIKE, then what it costs.
588
+ */
589
+ {
590
+ id: "details",
591
+ label: "Details",
592
+ required: true,
593
+ fields: ["authenticity", "features", "specifications", "weightG", "dimensionsCm"],
594
+ render: ({ values, onChange }) => (_jsx(StepDetails, { values: values, onChange: onChange, listingType: listingType, ...(storeSlug ? { storeId: storeSlug } : {}) })),
595
+ /*
596
+ * The one required field in the section, and the message says what to do
597
+ * rather than naming the field — "Authenticity is required" tells a
598
+ * seller nothing they cannot see.
599
+ */
600
+ schema: z
601
+ .object({
602
+ authenticity: z.enum(["original", "reproduction", "unverified"], {
603
+ errorMap: () => ({
604
+ message: "Say whether this is an original, a reproduction, or that you cannot confirm",
605
+ }),
606
+ }),
607
+ })
608
+ .passthrough(),
609
+ },
519
610
  ...(typeSpecificSection ? [typeSpecificSection] : []),
520
611
  {
521
612
  id: "pricing",
522
613
  label: "Pricing",
523
614
  required: true,
524
615
  quick: true,
525
- fields: ["price", "compareAtPrice", "stockQuantity", "allowOffers", "minOfferPercent", "gstRate", "hsnCode"],
616
+ // `gstRate` / `hsnCode` deliberately absent — admin-only, see StepPricing.
617
+ fields: ["price", "compareAtPrice", "stockQuantity", "allowOffers", "minOfferPercent"],
526
618
  render: ({ values, onChange }) => (_jsx(StepPricing, { values: values, onChange: onChange, listingType: listingType })),
527
619
  // Built per render because whether stock is required depends on the
528
620
  // listing type's own plugin — a digital code has no stock to speak of.
@@ -5,6 +5,18 @@ export interface SellerProductsFilterFieldsProps {
5
5
  id: string;
6
6
  label: string;
7
7
  }>;
8
+ /**
9
+ * The stock-level options, PASSED IN rather than declared here.
10
+ *
11
+ * They are derived from `LOW_STOCK_THRESHOLD`, and the predicate that reads
12
+ * the chosen value lives in `SellerProductsView` beside them — so declaring
13
+ * a second copy of the labels here is how the picker and the filter end up
14
+ * disagreeing about what "low" means.
15
+ */
16
+ stockStateOptions: {
17
+ value: string;
18
+ label: string;
19
+ }[];
8
20
  onChange: (next: Record<string, string>) => void;
9
21
  }
10
- export declare function SellerProductsFilterFields({ pendingFilters, statusOptions, onChange, }: SellerProductsFilterFieldsProps): React.JSX.Element;
22
+ export declare function SellerProductsFilterFields({ pendingFilters, statusOptions, stockStateOptions, onChange, }: SellerProductsFilterFieldsProps): React.JSX.Element;
@@ -12,7 +12,7 @@ const CONDITION_OPTIONS = [
12
12
  { value: "refurbished", label: "Refurbished" },
13
13
  { value: "for-parts", label: "For parts" },
14
14
  ];
15
- export function SellerProductsFilterFields({ pendingFilters, statusOptions, onChange, }) {
15
+ export function SellerProductsFilterFields({ pendingFilters, statusOptions, stockStateOptions, onChange, }) {
16
16
  const patch = (k, v) => onChange({ ...pendingFilters, [k]: v });
17
- return (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: statusOptions, value: pendingFilters.status ?? "", onChange: (id) => patch("status", id) }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", transform: "uppercase", children: "Category" }), _jsx(CategoryInlineSelect, { value: pendingFilters.category ?? "", onChange: (v) => patch("category", v), placeholder: "Search categories\u2026" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", transform: "uppercase", children: "Brand" }), _jsx(BrandInlineSelect, { value: pendingFilters.brand ?? "", onChange: (v) => patch("brand", v), placeholder: "Search brands\u2026", allowCreate: false })] }), _jsx(FieldSelect, { name: "condition", label: "Condition", value: pendingFilters.condition ?? "", onChange: (v) => patch("condition", v), options: CONDITION_OPTIONS }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", transform: "uppercase", children: "Price (\u20B9 Rupees)" }), _jsxs(Row, { gap: "sm", children: [_jsx(Div, { className: "flex-1", children: _jsx(FieldInput, { name: "minPrice", type: "number", min: 0, value: pendingFilters.minPrice ?? "", onChange: (v) => patch("minPrice", v), placeholder: "min" }) }), _jsx(Div, { className: "flex-1", children: _jsx(FieldInput, { name: "maxPrice", type: "number", min: 0, value: pendingFilters.maxPrice ?? "", onChange: (v) => patch("maxPrice", v), placeholder: "max" }) })] })] }), _jsx(FieldInput, { name: "tags", label: "Tags (comma-separated)", type: "text", value: pendingFilters.tags ?? "", onChange: (v) => patch("tags", v), placeholder: "pokemon, vintage, holo" }), _jsx(FieldInput, { name: "badges", label: "Badges (feature slugs)", type: "text", value: pendingFilters.badges ?? "", onChange: (v) => patch("badges", v), placeholder: "feature-free-shipping, feature-verified" })] }));
17
+ return (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: statusOptions, value: pendingFilters.status ?? "", onChange: (id) => patch("status", id) }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", transform: "uppercase", children: "Category" }), _jsx(CategoryInlineSelect, { value: pendingFilters.category ?? "", onChange: (v) => patch("category", v), placeholder: "Search categories\u2026" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", transform: "uppercase", children: "Brand" }), _jsx(BrandInlineSelect, { value: pendingFilters.brand ?? "", onChange: (v) => patch("brand", v), placeholder: "Search brands\u2026", allowCreate: false })] }), _jsx(FieldSelect, { name: "condition", label: "Condition", value: pendingFilters.condition ?? "", onChange: (v) => patch("condition", v), options: CONDITION_OPTIONS }), _jsxs(Stack, { gap: "xs", children: [_jsx(FieldSelect, { name: "stockState", label: "Stock level", value: pendingFilters.stockState ?? "", onChange: (v) => patch("stockState", v), options: stockStateOptions }), _jsx(Text, { size: "xs", color: "muted", children: "Applied to the listings on this page." })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", transform: "uppercase", children: "Price (\u20B9 Rupees)" }), _jsxs(Row, { gap: "sm", children: [_jsx(Div, { className: "flex-1", children: _jsx(FieldInput, { name: "minPrice", type: "number", min: 0, value: pendingFilters.minPrice ?? "", onChange: (v) => patch("minPrice", v), placeholder: "min" }) }), _jsx(Div, { className: "flex-1", children: _jsx(FieldInput, { name: "maxPrice", type: "number", min: 0, value: pendingFilters.maxPrice ?? "", onChange: (v) => patch("maxPrice", v), placeholder: "max" }) })] })] }), _jsx(FieldInput, { name: "tags", label: "Tags (comma-separated)", type: "text", value: pendingFilters.tags ?? "", onChange: (v) => patch("tags", v), placeholder: "pokemon, vintage, holo" }), _jsx(FieldInput, { name: "badges", label: "Badges (feature slugs)", type: "text", value: pendingFilters.badges ?? "", onChange: (v) => patch("badges", v), placeholder: "feature-free-shipping, feature-verified" })] }));
18
18
  }