@mohasinac/appkit 4.42.0 → 4.42.2

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 (185) hide show
  1. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +2 -30
  2. package/dist/_internal/server/features/offers/adapters.d.ts +29 -7
  3. package/dist/_internal/server/features/offers/adapters.js +14 -3
  4. package/dist/_internal/server/features/orders/adapters.js +54 -1
  5. package/dist/client.d.ts +1 -0
  6. package/dist/client.js +1 -0
  7. package/dist/features/account/components/UserOrdersView.js +30 -2
  8. package/dist/features/account/hooks/useAddresses.d.ts +21 -6
  9. package/dist/features/account/hooks/useAddresses.js +1 -1
  10. package/dist/features/addresses/hooks/usePostalLookup.js +84 -12
  11. package/dist/features/admin/components/AdminCategoriesView.js +37 -2
  12. package/dist/features/admin/components/AdminOffersView.js +2 -2
  13. package/dist/features/admin/constants/filter-tabs.d.ts +25 -0
  14. package/dist/features/admin/constants/filter-tabs.js +30 -0
  15. package/dist/features/admin/hooks/useAdminListingData.d.ts +35 -0
  16. package/dist/features/admin/hooks/useAdminListingData.js +45 -9
  17. package/dist/features/cart/schemas/index.d.ts +15 -0
  18. package/dist/features/cart/schemas/index.js +2 -0
  19. package/dist/features/cart/types/index.d.ts +22 -0
  20. package/dist/features/categories/components/CategoriesIndexListing.js +19 -1
  21. package/dist/features/categories/components/CategoriesIndexPageView.js +14 -1
  22. package/dist/features/classified/components/ClassifiedDetailPageView.d.ts +8 -0
  23. package/dist/features/classified/components/ClassifiedDetailPageView.js +1 -0
  24. package/dist/features/events/actions/event-actions.d.ts +13 -0
  25. package/dist/features/events/actions/event-actions.js +75 -1
  26. package/dist/features/events/repository/event-entry.repository.d.ts +17 -0
  27. package/dist/features/events/repository/event-entry.repository.js +36 -0
  28. package/dist/features/homepage/components/BrandsSection.js +1 -1
  29. package/dist/features/homepage/components/CustomerReviewsSection.js +1 -1
  30. package/dist/features/homepage/components/FeaturedBundlesSection.js +1 -1
  31. package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
  32. package/dist/features/layout/AppLayoutShell.js +2 -2
  33. package/dist/features/layout/BottomNavLayout.d.ts +9 -3
  34. package/dist/features/layout/BottomNavLayout.js +9 -3
  35. package/dist/features/media/upload/MediaUploadField.js +21 -3
  36. package/dist/features/orders/components/OrdersList.js +2 -1
  37. package/dist/features/orders/index.d.ts +1 -0
  38. package/dist/features/orders/index.js +1 -0
  39. package/dist/features/orders/types/index.d.ts +2 -0
  40. package/dist/features/orders/utils/order-ref.d.ts +45 -0
  41. package/dist/features/orders/utils/order-ref.js +58 -0
  42. package/dist/features/products/components/MakeOfferButton.d.ts +18 -1
  43. package/dist/features/products/components/MakeOfferButton.js +23 -1
  44. package/dist/features/products/components/ProductDetailPageView.d.ts +5 -0
  45. package/dist/features/products/components/ProductDetailPageView.js +3 -2
  46. package/dist/features/products/repository/products.repository.js +29 -2
  47. package/dist/features/seller/actions/offer-actions.js +32 -0
  48. package/dist/features/seller/components/QuickProductForm.js +15 -1
  49. package/dist/features/seller/components/SellerBidsView.js +3 -1
  50. package/dist/features/seller/components/SellerOffersView.js +2 -2
  51. package/dist/features/seller/components/SellerOrdersView.js +49 -6
  52. package/dist/features/seller/components/SellerProductsCards.js +23 -1
  53. package/dist/features/seller/components/SellerProductsView.js +27 -2
  54. package/dist/features/seller/hooks/useSellerListingData.d.ts +0 -1
  55. package/dist/features/seller/hooks/useSellerListingData.js +17 -19
  56. package/dist/features/shell/SectionForm.js +33 -2
  57. package/dist/features/tester/seed-data/_happy-path.d.ts +23 -0
  58. package/dist/features/tester/seed-data/_happy-path.js +710 -0
  59. package/dist/features/tester/seed-data/_money-flows.js +34 -23
  60. package/dist/features/tester/seed-data/authored/account-auth__profile-settings.js +22 -3
  61. package/dist/features/tester/seed-data/authored/addresses__address-filters.js +17 -12
  62. package/dist/features/tester/seed-data/authored/addresses__postal-lookup.js +7 -6
  63. package/dist/features/tester/seed-data/authored/addresses__postal-validation.js +10 -4
  64. package/dist/features/tester/seed-data/authored/addresses__state-picker.js +13 -8
  65. package/dist/features/tester/seed-data/authored/addresses__unban-request.js +49 -11
  66. package/dist/features/tester/seed-data/authored/admin__admin-detail-round-trips.js +10 -6
  67. package/dist/features/tester/seed-data/authored/admin__bans-and-trust.js +71 -32
  68. package/dist/features/tester/seed-data/authored/admin__blog-faqs.js +80 -31
  69. package/dist/features/tester/seed-data/authored/admin__bug-hunter-rewards.js +62 -25
  70. package/dist/features/tester/seed-data/authored/admin__bulk-actions.js +61 -31
  71. package/dist/features/tester/seed-data/authored/admin__bundles.js +84 -37
  72. package/dist/features/tester/seed-data/authored/admin__buyer-data-admin.js +90 -57
  73. package/dist/features/tester/seed-data/authored/admin__catalog-listings.js +38 -26
  74. package/dist/features/tester/seed-data/authored/admin__category-brand-authoring.js +47 -28
  75. package/dist/features/tester/seed-data/authored/admin__classifieds-digitalcodes-live.js +118 -31
  76. package/dist/features/tester/seed-data/authored/admin__content-deletes.js +30 -24
  77. package/dist/features/tester/seed-data/authored/admin__content-marketing.js +83 -46
  78. package/dist/features/tester/seed-data/authored/admin__coupons.js +8 -8
  79. package/dist/features/tester/seed-data/authored/admin__events-raffles-spin.js +9 -9
  80. package/dist/features/tester/seed-data/authored/admin__firebase-function-effects.js +19 -4
  81. package/dist/features/tester/seed-data/authored/admin__media-watermark.js +77 -33
  82. package/dist/features/tester/seed-data/authored/admin__orders-fulfillment.js +5 -5
  83. package/dist/features/tester/seed-data/authored/admin__prize-draws-lotteries.js +29 -5
  84. package/dist/features/tester/seed-data/authored/admin__site-system.js +45 -23
  85. package/dist/features/tester/seed-data/authored/admin__uncovered-admin-pages.js +53 -28
  86. package/dist/features/tester/seed-data/authored/admin__users-trust.js +20 -20
  87. package/dist/features/tester/seed-data/authored/buying__bidding.js +19 -2
  88. package/dist/features/tester/seed-data/authored/buying__browsing-search.js +20 -5
  89. package/dist/features/tester/seed-data/authored/buying__buying-checkout.js +28 -10
  90. package/dist/features/tester/seed-data/authored/buying__cart.js +10 -3
  91. package/dist/features/tester/seed-data/authored/buying__image-tile-layout.js +0 -16
  92. package/dist/features/tester/seed-data/authored/buying__my-orders.js +27 -5
  93. package/dist/features/tester/seed-data/authored/buying__offers.js +22 -7
  94. package/dist/features/tester/seed-data/authored/buying__order-detail-actions.js +3 -3
  95. package/dist/features/tester/seed-data/authored/buying__product-detail.js +35 -4
  96. package/dist/features/tester/seed-data/authored/buying__return-request.js +19 -6
  97. package/dist/features/tester/seed-data/authored/buying__reviews-pagination.js +36 -18
  98. package/dist/features/tester/seed-data/authored/buying__reviews.js +16 -1
  99. package/dist/features/tester/seed-data/authored/buying__user-dashboard-navigation.js +8 -6
  100. package/dist/features/tester/seed-data/authored/buying__user-uncovered-pages.js +16 -1
  101. package/dist/features/tester/seed-data/authored/community-support__public-profile.js +3 -2
  102. package/dist/features/tester/seed-data/authored/community-support__support-tickets.js +4 -4
  103. package/dist/features/tester/seed-data/authored/content-discovery__blog.js +19 -2
  104. package/dist/features/tester/seed-data/authored/content-discovery__category-brand-relations.js +4 -4
  105. package/dist/features/tester/seed-data/authored/content-discovery__category-counts-and-rollup.js +5 -5
  106. package/dist/features/tester/seed-data/authored/content-discovery__coupons.js +41 -6
  107. package/dist/features/tester/seed-data/authored/content-discovery__event-detail-subroutes.js +63 -8
  108. package/dist/features/tester/seed-data/authored/content-discovery__event-participation.js +29 -29
  109. package/dist/features/tester/seed-data/authored/content-discovery__events.js +2 -2
  110. package/dist/features/tester/seed-data/authored/content-discovery__faq-help.js +23 -1
  111. package/dist/features/tester/seed-data/authored/content-discovery__notifications.js +6 -6
  112. package/dist/features/tester/seed-data/authored/content-discovery__search.js +1 -1
  113. package/dist/features/tester/seed-data/authored/content-discovery__store-reviews-aggregate.js +1 -1
  114. package/dist/features/tester/seed-data/authored/cta-layout__checkout-bottom-bar.js +31 -6
  115. package/dist/features/tester/seed-data/authored/cta-layout__dialog-footers.js +34 -10
  116. package/dist/features/tester/seed-data/authored/cta-layout__editor-action-bar.js +35 -8
  117. package/dist/features/tester/seed-data/authored/cta-layout__navbar-ctas.js +31 -9
  118. package/dist/features/tester/seed-data/authored/cta-layout__product-bottom-bar.js +29 -3
  119. package/dist/features/tester/seed-data/authored/design-ux__back-to-top-button.js +1 -1
  120. package/dist/features/tester/seed-data/authored/design-ux__carousel-arrow-bounds.js +10 -10
  121. package/dist/features/tester/seed-data/authored/design-ux__dashboard-layout.js +3 -3
  122. package/dist/features/tester/seed-data/authored/design-ux__footer-theme.js +2 -2
  123. package/dist/features/tester/seed-data/authored/design-ux__form-validation-errors.js +2 -2
  124. package/dist/features/tester/seed-data/authored/design-ux__general-design.js +4 -4
  125. package/dist/features/tester/seed-data/authored/design-ux__hand-mode-layout.js +4 -4
  126. package/dist/features/tester/seed-data/authored/design-ux__homepage-carousels.js +8 -8
  127. package/dist/features/tester/seed-data/authored/design-ux__status-badge-legibility.js +2 -2
  128. package/dist/features/tester/seed-data/authored/design-ux__sticky-cta-bar.js +2 -2
  129. package/dist/features/tester/seed-data/authored/page-wiring__data-loss.js +39 -16
  130. package/dist/features/tester/seed-data/authored/page-wiring__detail-pages.js +43 -8
  131. package/dist/features/tester/seed-data/authored/page-wiring__drawer-pages.js +40 -14
  132. package/dist/features/tester/seed-data/authored/page-wiring__reachability.js +31 -14
  133. package/dist/features/tester/seed-data/authored/public-pages__auth-error-pages.js +7 -6
  134. package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.js +14 -5
  135. package/dist/features/tester/seed-data/authored/public-pages__core-listing-pages.js +3 -3
  136. package/dist/features/tester/seed-data/authored/public-pages__help-how-it-works.js +1 -1
  137. package/dist/features/tester/seed-data/authored/public-pages__help-scams-guides-subpages.js +4 -2
  138. package/dist/features/tester/seed-data/authored/public-pages__legal-policy-pages.js +3 -3
  139. package/dist/features/tester/seed-data/authored/public-pages__newly-wired-browse-indexes.js +5 -5
  140. package/dist/features/tester/seed-data/authored/public-pages__store-tabs.js +5 -5
  141. package/dist/features/tester/seed-data/authored/public-pages__stores-sellers-directories.js +3 -3
  142. package/dist/features/tester/seed-data/authored/search-and-nav__employee-permissions.js +45 -11
  143. package/dist/features/tester/seed-data/authored/search-and-nav__header-search.js +57 -17
  144. package/dist/features/tester/seed-data/authored/search-and-nav__settings-deep-links.js +36 -5
  145. package/dist/features/tester/seed-data/authored/search-and-nav__sidebar-search.js +29 -4
  146. package/dist/features/tester/seed-data/authored/selling__become-seller.js +23 -0
  147. package/dist/features/tester/seed-data/authored/selling__final-sale-authoring.js +14 -13
  148. package/dist/features/tester/seed-data/authored/selling__listing-a-product.js +14 -9
  149. package/dist/features/tester/seed-data/authored/selling__listing-edit-roundtrip.js +34 -7
  150. package/dist/features/tester/seed-data/authored/selling__listing-lifecycle.js +17 -7
  151. package/dist/features/tester/seed-data/authored/selling__listing-type-fields-roundtrip.js +9 -9
  152. package/dist/features/tester/seed-data/authored/selling__media-limits.js +9 -4
  153. package/dist/features/tester/seed-data/authored/selling__product-upload-details.js +3 -3
  154. package/dist/features/tester/seed-data/authored/selling__quick-add-minimum-details.js +3 -3
  155. package/dist/features/tester/seed-data/authored/selling__sectionised-forms.js +25 -1
  156. package/dist/features/tester/seed-data/authored/selling__seller-analytics-payouts.js +27 -6
  157. package/dist/features/tester/seed-data/authored/selling__seller-bids-bundles-filters.js +19 -1
  158. package/dist/features/tester/seed-data/authored/selling__seller-catalog-org.js +63 -22
  159. package/dist/features/tester/seed-data/authored/selling__seller-custom-brands.js +9 -5
  160. package/dist/features/tester/seed-data/authored/selling__seller-guide.js +7 -6
  161. package/dist/features/tester/seed-data/authored/selling__seller-listing-types.js +3 -2
  162. package/dist/features/tester/seed-data/authored/selling__seller-marketing-extras.js +18 -18
  163. package/dist/features/tester/seed-data/authored/selling__seller-ops-comms.js +13 -12
  164. package/dist/features/tester/seed-data/authored/selling__seller-orders-at-scale.js +19 -3
  165. package/dist/features/tester/seed-data/authored/selling__seller-orders.js +22 -4
  166. package/dist/features/tester/seed-data/authored/selling__seller-shipping-payouts-setup.js +20 -8
  167. package/dist/features/tester/seed-data/authored/selling__store-dashboard-navigation.js +10 -9
  168. package/dist/features/tester/seed-data/authored/selling__store-uncovered-pages.js +2 -2
  169. package/dist/features/tester/seed-data/authored/seo__canonical-and-host.js +5 -5
  170. package/dist/features/tester/seed-data/authored/seo__og-images.js +1 -1
  171. package/dist/features/tester/seed-data/authored/seo__page-metadata.js +7 -7
  172. package/dist/features/tester/seed-data/authored/seo__sitemap-and-robots.js +3 -3
  173. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +126 -24
  174. package/dist/features/tester/utils/phases.d.ts +17 -0
  175. package/dist/features/tester/utils/phases.js +29 -2
  176. package/dist/index.d.ts +1 -0
  177. package/dist/index.js +2 -0
  178. package/dist/seo/json-ld.d.ts +23 -0
  179. package/dist/seo/json-ld.js +3 -2
  180. package/dist/server.d.ts +1 -0
  181. package/dist/server.js +2 -0
  182. package/dist/styles.css +1 -1
  183. package/dist/tailwind-utilities.css +1 -1
  184. package/dist/ui/components/PaginatedSelect.js +0 -0
  185. package/package.json +1 -1
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Badge, Button, Checkbox, Div, Grid, Row, Stack, Text, TextLink } from "../../../ui";
4
4
  import { MediaImage } from "../../media/MediaImage";
5
5
  import { ROW_ACTION_META, ROW_ACTION_ID } from "../../../features/products/constants/action-defs";
6
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
6
7
  import { listingBadgeVariant } from "../../products/utils/listing-badge-variant";
7
8
  const __P = {
8
9
  p3: "p-[var(--appkit-space-3)]",
@@ -17,6 +18,27 @@ function buildHref(row) {
17
18
  return `/pre-orders/${row.id}`;
18
19
  return `/products/${row.id}`;
19
20
  }
21
+ /*
22
+ * 🛑 THE DELETE BUTTONS BELOW CARRY `action`, AND THAT IS WHAT CONFIRMS THEM.
23
+ *
24
+ * They took their LABEL from `ROW_ACTION_META[ROW_ACTION_ID.DELETE]` and
25
+ * passed no `action` prop, so `<Button>`'s confirmation gate — which fires
26
+ * only when `action.confirmation` is set — never engaged. One click on a
27
+ * row's Delete permanently removed a PUBLISHED listing with no dialog of any
28
+ * kind; measured on a real seller account, the row was gone immediately and
29
+ * still gone after a reload.
30
+ *
31
+ * Using the registry for the label while bypassing it for the confirmation is
32
+ * exactly the split Rule #7 describes, on the one action where that rule says
33
+ * the cost is data loss. `ACTIONS.STORE["delete-listing"]` already carried the
34
+ * copy ("Delete listing? / This listing will be permanently removed. This
35
+ * action cannot be undone.") and the bulk bar already resolved it correctly;
36
+ * only the row path did not.
37
+ *
38
+ * `variant="ghost"` stays explicit so the row keeps its quiet styling: Button
39
+ * resolves the variant as `variant ?? kindDefault`, so the caller wins and the
40
+ * danger kind drives the dialog without turning the row button red.
41
+ */
20
42
  export function SellerProductsCards({ view, rows, isLoading, listingKind, selectedIds, toggle, onEdit, onDuplicate, onDelete, }) {
21
43
  const Container = view === "grid" ? Grid : Stack;
22
44
  const containerProps = view === "grid"
@@ -25,6 +47,6 @@ export function SellerProductsCards({ view, rows, isLoading, listingKind, select
25
47
  return (_jsxs(Container, { ...containerProps, children: [rows.length === 0 && !isLoading && (_jsx(Text, { className: "text-[var(--appkit-color-text-muted)] col-span-full", size: "sm", children: listingKind !== "all" ? `No ${listingKind} listings found` : "No products listed yet" })), rows.map((row) => {
26
48
  const isSelected = selectedIds.has(row.id);
27
49
  const href = buildHref(row);
28
- return view === "grid" ? (_jsxs(Div, { className: "group relative bg-[var(--appkit-color-surface)] overflow-hidden", shadow: "hover-md", rounded: "xl", border: isSelected ? "strong" : "default", children: [_jsx(Div, { className: "absolute top-2 left-2 z-10", children: _jsx(Checkbox, { bare: true, checked: isSelected, onChange: () => toggle(row.id), className: "h-4 w-4 rounded border-[var(--appkit-color-border)]", onClick: (e) => e.stopPropagation(), "aria-label": "Select" }) }), _jsxs(TextLink, { href: href, className: "block", children: [_jsx(Div, { className: "w-full aspect-square", children: _jsx(MediaImage, { src: row.imageUrl, alt: row.primary, size: "card" }) }), _jsxs(Stack, { className: `${__P.p3}`, gap: "xs", children: [_jsx(Text, { className: "line-clamp-1", size: "sm", weight: "medium", children: row.primary }), _jsxs(Row, { gap: "sm", children: [_jsx(Badge, { variant: listingBadgeVariant(row.listingKind), children: row.listingKind }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] line-clamp-1", size: "xs", children: row.secondary })] })] })] }), _jsxs(Row, { className: "border-t border-[var(--appkit-color-border)]", justify: "end", gap: "xs", padding: "xs", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); onEdit(row); }, "aria-label": "Edit", children: ROW_ACTION_META[ROW_ACTION_ID.EDIT].label }), _jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); onDuplicate(row); }, "aria-label": "Duplicate", children: ROW_ACTION_META[ROW_ACTION_ID.DUPLICATE].label }), onDelete && (_jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); onDelete(row); }, "aria-label": "Delete", children: ROW_ACTION_META[ROW_ACTION_ID.DELETE].label }))] })] }, row.id)) : (_jsxs(Row, { className: "bg-[var(--appkit-color-surface)] hover:bg-[var(--appkit-color-surface-raised)]", align: "center", gap: "3", rounded: "lg", padding: "inlineSm", border: isSelected ? "strong" : "default", children: [_jsx(Checkbox, { bare: true, checked: isSelected, onChange: () => toggle(row.id), className: "h-4 w-4 rounded border-[var(--appkit-color-border)]", "aria-label": "Select" }), _jsx(Div, { className: `w-12 h-12 ${__O.hidden}`, rounded: "default", children: _jsx(MediaImage, { src: row.imageUrl, alt: row.primary, size: "thumbnail" }) }), _jsxs(TextLink, { href: href, className: "flex-1 min-w-0", children: [_jsx(Text, { className: "line-clamp-1", size: "sm", weight: "medium", children: row.primary }), _jsxs(Row, { gap: "sm", children: [_jsx(Badge, { variant: listingBadgeVariant(row.listingKind), children: row.listingKind }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] line-clamp-1", size: "xs", children: row.secondary })] })] }), _jsxs(Row, { className: "flex-shrink-0", gap: "xs", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: () => onEdit(row), "aria-label": "Edit", children: ROW_ACTION_META[ROW_ACTION_ID.EDIT].label }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => onDuplicate(row), "aria-label": "Duplicate", children: ROW_ACTION_META[ROW_ACTION_ID.DUPLICATE].label }), onDelete && (_jsx(Button, { variant: "ghost", size: "sm", onClick: () => onDelete(row), "aria-label": "Delete", children: ROW_ACTION_META[ROW_ACTION_ID.DELETE].label }))] })] }, row.id));
50
+ return view === "grid" ? (_jsxs(Div, { className: "group relative bg-[var(--appkit-color-surface)] overflow-hidden", shadow: "hover-md", rounded: "xl", border: isSelected ? "strong" : "default", children: [_jsx(Div, { className: "absolute top-2 left-2 z-10", children: _jsx(Checkbox, { bare: true, checked: isSelected, onChange: () => toggle(row.id), className: "h-4 w-4 rounded border-[var(--appkit-color-border)]", onClick: (e) => e.stopPropagation(), "aria-label": "Select" }) }), _jsxs(TextLink, { href: href, className: "block", children: [_jsx(Div, { className: "w-full aspect-square", children: _jsx(MediaImage, { src: row.imageUrl, alt: row.primary, size: "card" }) }), _jsxs(Stack, { className: `${__P.p3}`, gap: "xs", children: [_jsx(Text, { className: "line-clamp-1", size: "sm", weight: "medium", children: row.primary }), _jsxs(Row, { gap: "sm", children: [_jsx(Badge, { variant: listingBadgeVariant(row.listingKind), children: row.listingKind }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] line-clamp-1", size: "xs", children: row.secondary })] })] })] }), _jsxs(Row, { className: "border-t border-[var(--appkit-color-border)]", justify: "end", gap: "xs", padding: "xs", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); onEdit(row); }, "aria-label": "Edit", children: ROW_ACTION_META[ROW_ACTION_ID.EDIT].label }), _jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); onDuplicate(row); }, "aria-label": "Duplicate", children: ROW_ACTION_META[ROW_ACTION_ID.DUPLICATE].label }), onDelete && (_jsx(Button, { variant: "ghost", size: "sm", action: ACTIONS.STORE["delete-listing"], onClick: (e) => { e.stopPropagation(); onDelete(row); }, "aria-label": "Delete", children: ROW_ACTION_META[ROW_ACTION_ID.DELETE].label }))] })] }, row.id)) : (_jsxs(Row, { className: "bg-[var(--appkit-color-surface)] hover:bg-[var(--appkit-color-surface-raised)]", align: "center", gap: "3", rounded: "lg", padding: "inlineSm", border: isSelected ? "strong" : "default", children: [_jsx(Checkbox, { bare: true, checked: isSelected, onChange: () => toggle(row.id), className: "h-4 w-4 rounded border-[var(--appkit-color-border)]", "aria-label": "Select" }), _jsx(Div, { className: `w-12 h-12 ${__O.hidden}`, rounded: "default", children: _jsx(MediaImage, { src: row.imageUrl, alt: row.primary, size: "thumbnail" }) }), _jsxs(TextLink, { href: href, className: "flex-1 min-w-0", children: [_jsx(Text, { className: "line-clamp-1", size: "sm", weight: "medium", children: row.primary }), _jsxs(Row, { gap: "sm", children: [_jsx(Badge, { variant: listingBadgeVariant(row.listingKind), children: row.listingKind }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] line-clamp-1", size: "xs", children: row.secondary })] })] }), _jsxs(Row, { className: "flex-shrink-0", gap: "xs", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: () => onEdit(row), "aria-label": "Edit", children: ROW_ACTION_META[ROW_ACTION_ID.EDIT].label }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => onDuplicate(row), "aria-label": "Duplicate", children: ROW_ACTION_META[ROW_ACTION_ID.DUPLICATE].label }), onDelete && (_jsx(Button, { variant: "ghost", size: "sm", action: ACTIONS.STORE["delete-listing"], onClick: () => onDelete(row), "aria-label": "Delete", children: ROW_ACTION_META[ROW_ACTION_ID.DELETE].label }))] })] }, row.id));
29
51
  })] }));
30
52
  }
@@ -17,7 +17,9 @@ import { SELLER_PRODUCT_STATUS_TABS } from "../../admin/constants/filter-tabs";
17
17
  import { ROUTES } from "../../../constants";
18
18
  import { normalizeListingType } from "../../products/utils/listing-type";
19
19
  import { useAvailabilityScope } from "../../products/hooks/useAvailabilityScope";
20
- import { toRecordArray, toRelativeDate, toStringValue } from "../hooks/useSellerListingData";
20
+ import { toRecordArray, toStringValue } from "../hooks/useSellerListingData";
21
+ // Defining module (Root Cause #18) — the seller-side duplicate was deleted for drift.
22
+ import { toRelativeDate } from "../../admin/hooks/useAdminListingData";
21
23
  import { useListingTypeFlags } from "../../../react/hooks/useListingTypeFlags";
22
24
  import { ALL_LISTING_TYPES } from "../../../_internal/shared/listing-types/feature-flags";
23
25
  import { pluginFor } from "../../../_internal/shared/listing-types/_registry";
@@ -267,17 +269,40 @@ export function SellerProductsView({ onDeleteProduct, onCreateClick, }) {
267
269
  * the UI simply never presented it.
268
270
  */
269
271
  const statusLabel = toStringValue(item.status, "draft");
272
+ /*
273
+ * 🛑 PRICE AND STOCK GO IN THE VISIBLE LINE TOO — same reason as the
274
+ * status note above, one field over.
275
+ *
276
+ * `price` was already mapped onto the row and correct, and this view
277
+ * renders only `primary`, `secondary`, the type chip and the action
278
+ * buttons — so it was never displayed. Measured on a real seller
279
+ * account with 127 listings: ZERO rupee amounts and zero stock figures
280
+ * anywhere in the list. To learn what one of their own listings costs
281
+ * or how many are left, a seller had to open it.
282
+ *
283
+ * Those are the two numbers a seller scans for, so price leads.
284
+ * `stockQuantity` was not mapped at all and is added here.
285
+ *
286
+ * An auction keeps its own summary (reserve / bids / ends) and is not
287
+ * given a list price: for an auction the meaningful money is the
288
+ * reserve and the current bid, both already in `auctionSecondary`, and
289
+ * a "price" beside them would read as a third, competing figure.
290
+ */
291
+ const stockRaw = typeof item.stockQuantity === "number" ? item.stockQuantity : null;
292
+ const priceLabel = kind !== "auction" && priceRaw ? `₹${priceRaw.toLocaleString("en-IN")}` : "";
293
+ const stockLabel = stockRaw === null ? "" : `${stockRaw} in stock`;
270
294
  const secondaryBase = auctionSecondary || toStringValue(item.condition, "");
271
295
  return {
272
296
  id: toStringValue(item.id, `product-${index}`),
273
297
  primary: toStringValue(item.title ?? item.name, "Untitled product"),
274
- secondary: [secondaryBase, statusLabel].filter(Boolean).join(" · "),
298
+ secondary: [priceLabel, stockLabel, secondaryBase, statusLabel].filter(Boolean).join(" · "),
275
299
  status: statusLabel,
276
300
  updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
277
301
  imageUrl: toStringValue(item.mainImage ?? item.images?.[0], undefined),
278
302
  image: toStringValue(item.mainImage ?? item.images?.[0], undefined),
279
303
  listingKind: kind,
280
304
  price: priceRaw ? `₹${priceRaw.toLocaleString("en-IN")}` : "—",
305
+ stock: stockLabel || "—",
281
306
  physicalLocation: item.physicalLocation &&
282
307
  typeof item.physicalLocation.zone === "string"
283
308
  ? item.physicalLocation
@@ -14,5 +14,4 @@ type UnknownRecord = Record<string, JsonValue>;
14
14
  export declare function toRecordArray(value: unknown): UnknownRecord[];
15
15
  export declare function toStringValue(value: unknown, fallback?: string): string;
16
16
  export declare function toCurrency(value: unknown): string;
17
- export declare function toRelativeDate(value: unknown): string;
18
17
  export {};
@@ -18,25 +18,23 @@ export function toCurrency(value) {
18
18
  }
19
19
  return formatCurrency(value);
20
20
  }
21
- export function toRelativeDate(value) {
22
- const date = parseDate(value);
23
- if (!date) {
24
- return "-";
25
- }
26
- const deltaMs = Date.now() - date.getTime();
27
- const minute = 60000;
28
- const hour = 60 * minute;
29
- const day = 24 * hour;
30
- if (deltaMs < minute)
31
- return "just now";
32
- if (deltaMs < hour)
33
- return `${Math.floor(deltaMs / minute)}m ago`;
34
- if (deltaMs < day)
35
- return `${Math.floor(deltaMs / hour)}h ago`;
36
- if (deltaMs < 7 * day)
37
- return `${Math.floor(deltaMs / day)}d ago`;
38
- return date.toLocaleDateString("en-IN", { year: "numeric", month: "short", day: "numeric" });
39
- }
21
+ /*
22
+ * 🛑 `toRelativeDate` WAS HERE AND IS GONE. Import it from
23
+ * `features/admin/hooks/useAdminListingData` — the defining module now, and
24
+ * the one 44 files already use (Root Cause #18: import from where a symbol is
25
+ * defined, never from a convenience copy).
26
+ *
27
+ * This copy had drifted from that one and the drift was live: for a FUTURE
28
+ * date, `deltaMs` is negative, so `deltaMs < minute` matched and this version
29
+ * answered **"just now"** while the admin version answered **"1m ago"** — two
30
+ * different wrong answers to "when is this deadline", depending on which file
31
+ * a view happened to import from. The admin one is fixed to say "in 48h"; a
32
+ * second implementation is how that fix would have been half-applied.
33
+ *
34
+ * The three sibling helpers below are also duplicated there. They are left
35
+ * alone deliberately: none of them is wrong, and collapsing them is an import
36
+ * rewrite across both feature trees rather than a bug fix.
37
+ */
40
38
  function parseDate(value) {
41
39
  if (value instanceof Date)
42
40
  return value;
@@ -10,6 +10,9 @@ import { useFormBottomActions } from "../layout/hooks/useFormBottomActions";
10
10
  import { useSectionState } from "../account/hooks/useCollapsedSections";
11
11
  import { FormSchemaContext } from "./FormShell";
12
12
  import { EASE_OUT_MS } from "../../tokens/motion";
13
+ /* Stable identity — a fresh `{}` per render would break any memo a section
14
+ * render does on its `errors` prop. */
15
+ const EMPTY_ERRORS = {};
13
16
  /** Sections in render order: required first, original order preserved within each group. */
14
17
  export function orderSections(sections) {
15
18
  return [...sections.filter((s) => s.required), ...sections.filter((s) => !s.required)];
@@ -118,6 +121,27 @@ export function SectionForm({ sections, values, onChange, onSubmit, submitLabel
118
121
  * a caller renders it outside a provider, which is legitimate.
119
122
  */
120
123
  const shellCtx = useContext(FormShellContext);
124
+ /*
125
+ * 🛑 Has the user ASKED to save yet?
126
+ *
127
+ * Root Cause #74 gated `FormErrorSummary` on this and stopped there. The
128
+ * per-section "N issues" badges below were never gated, and the eleven views
129
+ * that mount SectionForm all call `validate(draft)` from a MOUNT EFFECT —
130
+ * which that entry is explicit must STAY, because it is what keeps the errors
131
+ * current as the user fixes fields after a failed save. So `fieldErrors` is
132
+ * fully populated on first paint, and "Add New Address" opened reading
133
+ * "3 issues" and "4 issues" in red against a form nobody had typed into.
134
+ *
135
+ * Gate the DISPLAY, never the computation — deleting the mount-effect
136
+ * validate is the obvious repair and it is the wrong one.
137
+ *
138
+ * Local state rather than `shellCtx.submitAttempted` alone: SectionForm owns
139
+ * the submit button, so it always knows, whereas `shellCtx` is legitimately
140
+ * null when a caller renders it outside a provider (see above). Both are
141
+ * consulted, so a provider-level attempt from the pinned mobile bar counts too.
142
+ */
143
+ const [attemptedHere, setAttemptedHere] = useState(false);
144
+ const submitAttempted = attemptedHere || shellCtx?.submitAttempted === true;
121
145
  const [fieldErrors, setFieldErrors] = useState({});
122
146
  const [uncontrolledOpenIds, setUncontrolledOpenIds] = useState(() => orderSections(sections).filter((s) => s.required).map((s) => s.id));
123
147
  /*
@@ -251,6 +275,7 @@ export function SectionForm({ sections, values, onChange, onSubmit, submitLabel
251
275
  * identically whichever the user presses.
252
276
  */
253
277
  const handleSubmit = useCallback(() => {
278
+ setAttemptedHere(true);
254
279
  shellCtx?.markSubmitAttempted();
255
280
  /*
256
281
  * Reveal the FIRST offending section — only the first. Expanding all five
@@ -328,9 +353,15 @@ export function SectionForm({ sections, values, onChange, onSubmit, submitLabel
328
353
  * <button><div><span><div> — invalid, since a button may only
329
354
  * contain phrasing content. Same variant props, legal element.
330
355
  */
331
- _jsxs(Row, { as: "span", align: "center", gap: "xs", className: "min-w-0", children: [_jsx(Span, { weight: "semibold", size: "sm", children: section.heading ?? section.label }), isRequired && _jsx(Span, { size: "xs", color: "muted", children: "Required" })] }), isCollapsed: isCollapsed, collapsible: !isRequired, onToggle: () => toggle(section.id), keepMounted: section.keepMounted, renderHeaderExtra: errorCount > 0
356
+ _jsxs(Row, { as: "span", align: "center", gap: "xs", className: "min-w-0", children: [_jsx(Span, { weight: "semibold", size: "sm", children: section.heading ?? section.label }), isRequired && _jsx(Span, { size: "xs", color: "muted", children: "Required" })] }), isCollapsed: isCollapsed, collapsible: !isRequired, onToggle: () => toggle(section.id), keepMounted: section.keepMounted, renderHeaderExtra:
357
+ /* Only after the user has tried to save — see `submitAttempted` above. */
358
+ submitAttempted && errorCount > 0
332
359
  ? () => (_jsxs(Badge, { variant: "danger", children: [errorCount, " ", errorCount === 1 ? "issue" : "issues"] }))
333
- : undefined, children: section.render({ values, onChange: handleFieldChange, errors: fieldErrors }) }, section.id));
360
+ : undefined, children: section.render({
361
+ values,
362
+ onChange: handleFieldChange,
363
+ errors: submitAttempted ? fieldErrors : EMPTY_ERRORS,
364
+ }) }, section.id));
334
365
  }), !hideActions && (
335
366
  /*
336
367
  * `justify="between"` so a destructive action sits opposite Save
@@ -0,0 +1,23 @@
1
+ import type { TesterCaseRole } from "../schemas";
2
+ interface HappyPathCase {
3
+ key: string;
4
+ label: string;
5
+ /** Every role the case affects; per-role detail goes inline in the expectations. */
6
+ roles: TesterCaseRole[];
7
+ startPage?: string;
8
+ steps: string[];
9
+ inputs?: Record<string, string | number | boolean>;
10
+ expectedBehaviour: string;
11
+ expectedUiState: string;
12
+ expectedData?: Record<string, string | number | boolean>;
13
+ endResult: string;
14
+ href?: string;
15
+ }
16
+ interface HappyPathPage {
17
+ pageKey: string;
18
+ pageLabel: string;
19
+ href?: string;
20
+ cases: HappyPathCase[];
21
+ }
22
+ export declare const happyPathPages: HappyPathPage[];
23
+ export {};