@mohasinac/appkit 4.10.0 → 4.11.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 (319) hide show
  1. package/dist/_internal/client/features/layout/DashboardLayoutClient.d.ts +6 -1
  2. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +4 -1
  3. package/dist/_internal/client/features/lottery/LotteryPullForm.d.ts +1 -1
  4. package/dist/_internal/client/features/lottery/LotteryPullForm.js +2 -2
  5. package/dist/_internal/server/features/admin/daily-digest-actions.d.ts +18 -0
  6. package/dist/_internal/server/features/admin/daily-digest-actions.js +37 -0
  7. package/dist/_internal/server/features/audit-log/actions.d.ts +25 -0
  8. package/dist/_internal/server/features/audit-log/actions.js +38 -0
  9. package/dist/_internal/server/features/brands/actions.js +0 -2
  10. package/dist/_internal/server/features/brands/og.d.ts +4 -1
  11. package/dist/_internal/server/features/brands/og.js +1 -1
  12. package/dist/_internal/server/features/grouped/data.d.ts +4 -0
  13. package/dist/_internal/server/features/grouped/data.js +24 -0
  14. package/dist/_internal/server/features/grouped/index.d.ts +1 -1
  15. package/dist/_internal/server/features/grouped/index.js +1 -1
  16. package/dist/_internal/server/features/orders/actions.js +9 -0
  17. package/dist/_internal/server/features/orders/adapters.js +25 -0
  18. package/dist/_internal/server/functions/scheduled.d.ts +3 -1
  19. package/dist/_internal/server/functions/scheduled.js +17 -1
  20. package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +40 -0
  21. package/dist/_internal/server/jobs/core/dailyStatusDigest.js +164 -0
  22. package/dist/_internal/server/jobs/core/hardBanCascade.js +11 -0
  23. package/dist/_internal/server/jobs/core/testerSandboxCleanup.d.ts +1 -0
  24. package/dist/_internal/server/jobs/core/testerSandboxCleanup.js +4 -1
  25. package/dist/_internal/server/jobs/core/testerSandboxRefresh.d.ts +12 -0
  26. package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +85 -0
  27. package/dist/_internal/server/jobs/core/whatsappNotify.js +16 -0
  28. package/dist/_internal/server/jobs/handlers/dailyStatusDigest.d.ts +2 -0
  29. package/dist/_internal/server/jobs/handlers/dailyStatusDigest.js +4 -0
  30. package/dist/_internal/server/jobs/handlers/index.d.ts +2 -0
  31. package/dist/_internal/server/jobs/handlers/index.js +3 -0
  32. package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.d.ts +2 -0
  33. package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.js +2 -0
  34. package/dist/_internal/server/notifications/channel-health.d.ts +27 -0
  35. package/dist/_internal/server/notifications/channel-health.js +101 -0
  36. package/dist/_internal/shared/features/brands/schema.d.ts +0 -6
  37. package/dist/_internal/shared/features/brands/schema.js +0 -1
  38. package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +7 -0
  39. package/dist/_internal/shared/features/categories/bundle-schemas.js +3 -0
  40. package/dist/_internal/shared/features/orders/schema.d.ts +8 -8
  41. package/dist/client.d.ts +21 -6
  42. package/dist/client.js +13 -3
  43. package/dist/constants/api-endpoints.d.ts +6 -9
  44. package/dist/constants/api-endpoints.js +2 -3
  45. package/dist/contracts/client-auth.d.ts +3 -7
  46. package/dist/contracts/email.d.ts +1 -0
  47. package/dist/features/account/components/AddressForm.js +24 -2
  48. package/dist/features/account/components/BecomeSellerView.d.ts +1 -2
  49. package/dist/features/account/components/BecomeSellerView.js +1 -1
  50. package/dist/features/account/components/NotificationBell.js +1 -1
  51. package/dist/features/account/components/UserBidsView.d.ts +1 -0
  52. package/dist/features/account/components/UserBidsView.js +47 -0
  53. package/dist/features/account/components/UserOrdersView.d.ts +4 -11
  54. package/dist/features/account/components/UserOrdersView.js +54 -4
  55. package/dist/features/account/components/UserReturnsView.d.ts +4 -9
  56. package/dist/features/account/components/UserReturnsView.js +37 -3
  57. package/dist/features/account/components/index.d.ts +3 -2
  58. package/dist/features/account/components/index.js +1 -0
  59. package/dist/features/admin/actions/admin-actions.js +22 -0
  60. package/dist/features/admin/actions/admin-coupon-actions.js +10 -0
  61. package/dist/features/admin/actions/notification-actions.js +4 -3
  62. package/dist/features/admin/components/AdminAdsView.d.ts +2 -3
  63. package/dist/features/admin/components/AdminAdsView.js +86 -89
  64. package/dist/features/admin/components/AdminAllEventEntriesView.js +18 -3
  65. package/dist/features/admin/components/AdminAuditLogView.d.ts +11 -0
  66. package/dist/features/admin/components/AdminAuditLogView.js +95 -0
  67. package/dist/features/admin/components/AdminBlogEditorView.js +37 -3
  68. package/dist/features/admin/components/AdminBrandEditorView.js +10 -6
  69. package/dist/features/admin/components/AdminBundleEditorView.js +18 -2
  70. package/dist/features/admin/components/AdminCarouselEditorView.js +2 -2
  71. package/dist/features/admin/components/AdminCarouselGroupEditorView.js +2 -2
  72. package/dist/features/admin/components/AdminCartsView.js +34 -4
  73. package/dist/features/admin/components/AdminCategoryEditorView.js +5 -4
  74. package/dist/features/admin/components/AdminCouponEditorView.js +2 -2
  75. package/dist/features/admin/components/AdminFaqEditorView.js +2 -2
  76. package/dist/features/admin/components/AdminHistoryView.js +11 -1
  77. package/dist/features/admin/components/AdminNotificationsView.js +27 -1
  78. package/dist/features/admin/components/AdminOrderEditorView.d.ts +10 -1
  79. package/dist/features/admin/components/AdminOrderEditorView.js +4 -3
  80. package/dist/features/admin/components/AdminOrdersView.js +92 -24
  81. package/dist/features/admin/components/AdminPayoutMarkPaidModal.d.ts +7 -0
  82. package/dist/features/admin/components/AdminPayoutMarkPaidModal.js +47 -0
  83. package/dist/features/admin/components/AdminPayoutsView.js +15 -31
  84. package/dist/features/admin/components/AdminProductEditorView.js +26 -3
  85. package/dist/features/admin/components/AdminSiteSettingsView.js +20 -1
  86. package/dist/features/admin/components/AdminStoreAddressesView.js +22 -2
  87. package/dist/features/admin/components/AdminStoresView.js +7 -0
  88. package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +2 -2
  89. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +2 -2
  90. package/dist/features/admin/components/AdminUserEditorView.d.ts +2 -2
  91. package/dist/features/admin/components/AdminUserEditorView.js +4 -2
  92. package/dist/features/admin/components/AdminUsersView.js +1 -1
  93. package/dist/features/admin/components/AdminWishlistsView.js +14 -1
  94. package/dist/features/admin/components/DataListingView.d.ts +2 -2
  95. package/dist/features/admin/components/ViewAuditLogEntryModal.d.ts +19 -0
  96. package/dist/features/admin/components/ViewAuditLogEntryModal.js +22 -0
  97. package/dist/features/admin/components/ViewNotificationModal.d.ts +21 -0
  98. package/dist/features/admin/components/ViewNotificationModal.js +15 -0
  99. package/dist/features/admin/components/analytics/AdminPageViewsReportView.d.ts +4 -7
  100. package/dist/features/admin/components/analytics/AdminPageViewsReportView.js +56 -36
  101. package/dist/features/admin/components/index.d.ts +8 -1
  102. package/dist/features/admin/components/index.js +4 -0
  103. package/dist/features/admin/constants/filter-tabs.d.ts +49 -0
  104. package/dist/features/admin/constants/filter-tabs.js +27 -0
  105. package/dist/features/admin/hooks/useAdminListing.d.ts +6 -0
  106. package/dist/features/admin/hooks/useAdminListing.js +3 -1
  107. package/dist/features/admin/hooks/useAdminListingData.d.ts +9 -1
  108. package/dist/features/admin/hooks/useAdminListingData.js +9 -2
  109. package/dist/features/admin/repository/site-settings.repository.d.ts +5 -0
  110. package/dist/features/admin/schemas/firestore.d.ts +12 -0
  111. package/dist/features/analytics/types.d.ts +1 -1
  112. package/dist/features/analytics/types.js +8 -0
  113. package/dist/features/auctions/schemas/index.d.ts +5 -0
  114. package/dist/features/audit-log/repository/audit-log.repository.d.ts +45 -0
  115. package/dist/features/audit-log/repository/audit-log.repository.js +30 -0
  116. package/dist/features/audit-log/schemas/firestore.d.ts +59 -0
  117. package/dist/features/audit-log/schemas/firestore.js +35 -0
  118. package/dist/features/auth/actions/index.d.ts +0 -1
  119. package/dist/features/auth/actions/index.js +0 -1
  120. package/dist/features/auth/hooks/useAuth.d.ts +8 -35
  121. package/dist/features/auth/hooks/useAuth.js +21 -37
  122. package/dist/features/auth/permissions/constants.d.ts +1 -1
  123. package/dist/features/auth/permissions/constants.js +2 -0
  124. package/dist/features/blog/schemas/index.d.ts +32 -0
  125. package/dist/features/catalogue/components/AdminCatalogueApprovalsView.js +31 -3
  126. package/dist/features/catalogue/components/CatalogueItemEditorView.js +2 -1
  127. package/dist/features/categories/components/BrandDetailPageView.js +14 -12
  128. package/dist/features/categories/components/CategoryDetailPageView.js +7 -3
  129. package/dist/features/categories/components/CategoryHighlightsAndFaqSection.d.ts +14 -0
  130. package/dist/features/categories/components/CategoryHighlightsAndFaqSection.js +22 -0
  131. package/dist/features/categories/components/CategoryProductsView.d.ts +1 -5
  132. package/dist/features/categories/components/CategoryProductsView.js +1 -1
  133. package/dist/features/categories/components/CategorySelectorCreate.js +16 -2
  134. package/dist/features/categories/schemas/firestore.d.ts +19 -2
  135. package/dist/features/categories/schemas/index.d.ts +24 -3
  136. package/dist/features/categories/schemas/index.js +3 -1
  137. package/dist/features/categories/types/index.d.ts +7 -0
  138. package/dist/features/checkout/actions/checkout-value-otp-actions.d.ts +17 -3
  139. package/dist/features/checkout/actions/checkout-value-otp-actions.js +101 -22
  140. package/dist/features/contact/components/ContactForm.js +17 -4
  141. package/dist/features/contact/email.d.ts +1 -0
  142. package/dist/features/contact/email.js +3 -0
  143. package/dist/features/events/components/AdminEventEditorView.js +54 -2
  144. package/dist/features/events/components/AdminEventEntriesView.d.ts +2 -16
  145. package/dist/features/events/components/AdminEventEntriesView.js +56 -89
  146. package/dist/features/events/components/EventFormDrawer.js +5 -1
  147. package/dist/features/events/components/EventRaffleEntryForm.d.ts +1 -3
  148. package/dist/features/events/components/EventRaffleEntryForm.js +1 -1
  149. package/dist/features/events/schemas/index.d.ts +22 -0
  150. package/dist/features/grouped/repository/grouped-listings.repository.d.ts +4 -0
  151. package/dist/features/grouped/repository/grouped-listings.repository.js +18 -0
  152. package/dist/features/homepage/components/CharacterHotspotForm.js +202 -187
  153. package/dist/features/homepage/components/SectionCarousel.d.ts +1 -7
  154. package/dist/features/homepage/components/SectionCarousel.js +1 -1
  155. package/dist/features/layout/TitleBarLayout.d.ts +1 -1
  156. package/dist/features/layout/TitleBarLayout.js +3 -3
  157. package/dist/features/media/MediaImage.js +39 -3
  158. package/dist/features/media/types/index.d.ts +10 -0
  159. package/dist/features/media/types/index.js +1 -0
  160. package/dist/features/media/upload/MediaUploadField.d.ts +7 -1
  161. package/dist/features/media/upload/MediaUploadField.js +11 -7
  162. package/dist/features/media/upload/video-poster.d.ts +2 -0
  163. package/dist/features/media/upload/video-poster.js +16 -1
  164. package/dist/features/media/upload/video-poster.test.js +1 -1
  165. package/dist/features/orders/components/OrderStatusTimeline.d.ts +12 -0
  166. package/dist/features/orders/components/OrderStatusTimeline.js +55 -0
  167. package/dist/features/orders/components/index.d.ts +2 -0
  168. package/dist/features/orders/components/index.js +1 -0
  169. package/dist/features/orders/constants/payment-window.d.ts +25 -0
  170. package/dist/features/orders/constants/payment-window.js +25 -0
  171. package/dist/features/orders/repository/orders.repository.d.ts +32 -0
  172. package/dist/features/orders/repository/orders.repository.js +56 -0
  173. package/dist/features/orders/schemas/index.d.ts +24 -24
  174. package/dist/features/orders/types/index.d.ts +33 -0
  175. package/dist/features/pre-orders/schemas/index.d.ts +5 -0
  176. package/dist/features/products/components/BidHistory.js +9 -1
  177. package/dist/features/products/components/GroupSettingsPanel.js +25 -3
  178. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  179. package/dist/features/products/components/PrizeDrawEntryActions.d.ts +1 -4
  180. package/dist/features/products/components/PrizeDrawEntryActions.js +1 -1
  181. package/dist/features/products/components/ProductDetailActions.d.ts +1 -2
  182. package/dist/features/products/components/ProductDetailActions.js +1 -1
  183. package/dist/features/products/components/ProductGrid.js +15 -1
  184. package/dist/features/products/constants/action-defs.d.ts +1 -0
  185. package/dist/features/products/constants/action-defs.js +1 -0
  186. package/dist/features/products/schemas/index.d.ts +5 -0
  187. package/dist/features/promotions/components/CouponsIndexListing.d.ts +1 -3
  188. package/dist/features/promotions/components/CouponsIndexListing.js +1 -1
  189. package/dist/features/search/components/SearchView.d.ts +1 -3
  190. package/dist/features/search/components/SearchView.js +1 -1
  191. package/dist/features/seller/components/QuickProductForm.js +2 -1
  192. package/dist/features/seller/components/SellerBidsView.js +66 -107
  193. package/dist/features/seller/components/SellerGoogleReviewsView.js +17 -1
  194. package/dist/features/seller/components/SellerOrdersView.d.ts +15 -3
  195. package/dist/features/seller/components/SellerOrdersView.js +134 -163
  196. package/dist/features/seller/components/SellerPayoutMethodsView.js +60 -48
  197. package/dist/features/seller/components/SellerPayoutSettingsView.js +69 -4
  198. package/dist/features/seller/components/SellerPayoutsView.d.ts +12 -0
  199. package/dist/features/seller/components/SellerPayoutsView.js +25 -5
  200. package/dist/features/seller/components/SellerProductShell.d.ts +10 -3
  201. package/dist/features/seller/components/SellerProductShell.js +89 -36
  202. package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +2 -7
  203. package/dist/features/seller/components/SellerProductsFilterDrawer.js +4 -4
  204. package/dist/features/seller/components/SellerProductsView.d.ts +2 -4
  205. package/dist/features/seller/components/SellerProductsView.js +131 -189
  206. package/dist/features/seller/components/SellerReviewsView.js +95 -133
  207. package/dist/features/seller/components/SellerShippingConfigsView.js +47 -67
  208. package/dist/features/seller/components/SellerShippingView.js +41 -4
  209. package/dist/features/seller/components/SellerStoreCategoriesView.js +51 -59
  210. package/dist/features/seller/components/SellerStorefrontView.d.ts +6 -1
  211. package/dist/features/seller/components/SellerStorefrontView.js +42 -5
  212. package/dist/features/seller/components/index.d.ts +2 -2
  213. package/dist/features/seller/components/index.js +2 -2
  214. package/dist/features/shell/FormShell.d.ts +24 -4
  215. package/dist/features/shell/FormShell.js +12 -6
  216. package/dist/features/shell/QuickFormDrawer.js +67 -12
  217. package/dist/features/shell/StepForm.d.ts +30 -1
  218. package/dist/features/shell/StepForm.js +47 -4
  219. package/dist/features/shell/index.d.ts +1 -1
  220. package/dist/features/shell/index.js +1 -1
  221. package/dist/features/shipments/components/AdminShipmentEditorView.js +2 -2
  222. package/dist/features/shipments/schemas/validation.d.ts +14 -4
  223. package/dist/features/tester/components/AdminTesterFeedbackListView.js +23 -2
  224. package/dist/features/tester/components/TesterChecklistStepRow.js +1 -1
  225. package/dist/features/tester/components/TesterHubView.js +3 -2
  226. package/dist/features/tester/seed-data/orders-tester-seed-data.js +9 -1
  227. package/dist/features/tester/seed-data/products-tester-seed-data.js +43 -32
  228. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +404 -15
  229. package/dist/index.d.ts +15 -3
  230. package/dist/index.js +22 -5
  231. package/dist/next/routing/route-map.d.ts +11 -0
  232. package/dist/next/routing/route-map.js +5 -0
  233. package/dist/providers/email-resend/provider.js +1 -0
  234. package/dist/providers/firebase-client/auth.d.ts +1 -0
  235. package/dist/providers/firebase-client/auth.js +7 -1
  236. package/dist/repositories/index.d.ts +1 -0
  237. package/dist/repositories/index.js +1 -0
  238. package/dist/schemas/registry.d.ts +61 -25
  239. package/dist/security/pii-mask.js +1 -1
  240. package/dist/seed/categories-seed-data.js +69 -0
  241. package/dist/seed/products-auctions-seed-data.js +11 -0
  242. package/dist/seed/products-standard-seed-data.js +93 -4
  243. package/dist/seed/site-settings-seed-data.js +13 -1
  244. package/dist/seed/store-extensions-seed-data.js +1 -1
  245. package/dist/server-entry.d.ts +2 -1
  246. package/dist/server-entry.js +20 -2
  247. package/dist/server.d.ts +7 -2
  248. package/dist/server.js +8 -3
  249. package/dist/styles.css +1 -1
  250. package/dist/tailwind-utilities.css +1 -1
  251. package/dist/ui/components/Form.d.ts +11 -2
  252. package/dist/ui/components/Form.js +4 -3
  253. package/dist/ui/components/FormField.d.ts +3 -1
  254. package/dist/ui/components/FormField.js +22 -4
  255. package/dist/ui/components/ListingLayout.d.ts +1 -2
  256. package/dist/ui/components/ListingLayout.js +1 -1
  257. package/dist/ui/components/RecordDetailModal.d.ts +68 -0
  258. package/dist/ui/components/RecordDetailModal.js +8 -0
  259. package/dist/ui/components/SlottedListingView.js +9 -1
  260. package/dist/ui/forms/FormErrorSummary.d.ts +25 -0
  261. package/dist/ui/forms/FormErrorSummary.js +37 -0
  262. package/dist/ui/forms/FormShell.d.ts +37 -2
  263. package/dist/ui/forms/FormShell.js +37 -7
  264. package/dist/ui/forms/FormShell.style.css +17 -0
  265. package/dist/ui/forms/index.d.ts +2 -0
  266. package/dist/ui/forms/index.js +1 -0
  267. package/dist/ui/index.d.ts +4 -0
  268. package/dist/ui/index.js +5 -0
  269. package/dist/utils/media-url.d.ts +3 -1
  270. package/dist/utils/media-url.js +20 -1
  271. package/dist/validation/schemas.d.ts +4 -4
  272. package/package.json +1 -1
  273. package/dist/_internal/client/features/classified/ClassifiedDetailView.d.ts +0 -9
  274. package/dist/_internal/client/features/classified/ClassifiedDetailView.js +0 -55
  275. package/dist/_internal/client/features/digital-code/DigitalCodeDetailView.d.ts +0 -18
  276. package/dist/_internal/client/features/digital-code/DigitalCodeDetailView.js +0 -21
  277. package/dist/_internal/client/features/live/LiveItemDetailView.d.ts +0 -9
  278. package/dist/_internal/client/features/live/LiveItemDetailView.js +0 -25
  279. package/dist/_internal/server/jobs/core/prizeRevealClose.d.ts +0 -2
  280. package/dist/_internal/server/jobs/core/prizeRevealClose.js +0 -28
  281. package/dist/_internal/server/jobs/core/prizeRevealExpiry.d.ts +0 -2
  282. package/dist/_internal/server/jobs/core/prizeRevealExpiry.js +0 -61
  283. package/dist/_internal/server/jobs/core/prizeRevealOpen.d.ts +0 -2
  284. package/dist/_internal/server/jobs/core/prizeRevealOpen.js +0 -68
  285. package/dist/_internal/server/jobs/core/prizeRevealReminder.d.ts +0 -2
  286. package/dist/_internal/server/jobs/core/prizeRevealReminder.js +0 -49
  287. package/dist/_internal/server/jobs/handlers/prizeRevealClose.d.ts +0 -2
  288. package/dist/_internal/server/jobs/handlers/prizeRevealClose.js +0 -2
  289. package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.d.ts +0 -2
  290. package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.js +0 -2
  291. package/dist/_internal/server/jobs/handlers/prizeRevealOpen.d.ts +0 -2
  292. package/dist/_internal/server/jobs/handlers/prizeRevealOpen.js +0 -2
  293. package/dist/_internal/server/jobs/handlers/prizeRevealReminder.d.ts +0 -2
  294. package/dist/_internal/server/jobs/handlers/prizeRevealReminder.js +0 -2
  295. package/dist/features/auth/consent-otp.d.ts +0 -57
  296. package/dist/features/auth/consent-otp.js +0 -127
  297. package/dist/features/cart/components/CheckoutOtpModal.d.ts +0 -15
  298. package/dist/features/cart/components/CheckoutOtpModal.js +0 -12
  299. package/dist/features/checkout/actions/checkout-actions.d.ts +0 -31
  300. package/dist/features/checkout/actions/checkout-actions.js +0 -124
  301. package/dist/features/classified/components/ClassifiedIndexListing.d.ts +0 -5
  302. package/dist/features/classified/components/ClassifiedIndexListing.js +0 -123
  303. package/dist/features/classified/components/ClassifiedListView.d.ts +0 -6
  304. package/dist/features/classified/components/ClassifiedListView.js +0 -44
  305. package/dist/features/digital-codes/components/DigitalCodesIndexListing.d.ts +0 -5
  306. package/dist/features/digital-codes/components/DigitalCodesIndexListing.js +0 -114
  307. package/dist/features/digital-codes/components/DigitalCodesListView.d.ts +0 -6
  308. package/dist/features/digital-codes/components/DigitalCodesListView.js +0 -38
  309. package/dist/features/live/components/LiveItemsIndexListing.d.ts +0 -5
  310. package/dist/features/live/components/LiveItemsIndexListing.js +0 -115
  311. package/dist/features/live/components/LiveItemsListView.d.ts +0 -6
  312. package/dist/features/live/components/LiveItemsListView.js +0 -44
  313. package/dist/features/seller/components/SellerTemplatesView.d.ts +0 -12
  314. package/dist/features/seller/components/SellerTemplatesView.js +0 -276
  315. package/dist/react/hooks/useVisibleItems.d.ts +0 -37
  316. package/dist/react/hooks/useVisibleItems.js +0 -59
  317. package/dist/ui/components/TabStrip.d.ts +0 -34
  318. package/dist/ui/components/TabStrip.js +0 -56
  319. package/dist/ui/components/TabStrip.style.css +0 -97
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
  import type { ZodTypeAny } from "zod";
3
3
  import { type SurfaceProps } from "./surface-tokens";
4
- import { type UseFormShellStateResult } from "../forms/FormShell";
4
+ import { type FormShellContextValue, type UseFormShellStateResult } from "../forms/FormShell";
5
5
  export type FormSpacing = "none" | "xs" | "sm" | "md" | "lg" | "xl";
6
6
  export interface FormProps extends Omit<React.FormHTMLAttributes<HTMLFormElement>, "children">, SurfaceProps {
7
7
  /**
@@ -19,13 +19,22 @@ export interface FormProps extends Omit<React.FormHTMLAttributes<HTMLFormElement
19
19
  * boilerplate per callsite.
20
20
  */
21
21
  schema?: ZodTypeAny;
22
+ /**
23
+ * Use an externally-computed `FormShellContextValue` (e.g. from a parent's
24
+ * own `useFormShellState(schema)` call) instead of `<Form>` creating its
25
+ * own. Needed when other elements outside this `<form>` — a submit button
26
+ * in a separate sidebar, associated via the HTML `form` attribute — must
27
+ * share the exact same validation state; otherwise `<Form>`'s own internal
28
+ * provider would shadow the parent's for everything rendered as `children`.
29
+ */
30
+ shellCtx?: FormShellContextValue;
22
31
  /** Cross-axis alignment of flex/grid children. Replaces raw `items-*` className. */
23
32
  align?: "start" | "center" | "end" | "stretch";
24
33
  /** Gap between children — use instead of raw `gap-*` className. */
25
34
  gap?: GapToken;
26
35
  }
27
36
  export type GapToken = "none" | "xs" | "sm" | "md" | "lg" | "xl";
28
- export declare function Form({ children, spacing, schema, align, gap, surface, padding, paddingX, paddingY, rounded, roundedTop, roundedBottom, border, shadow, overflow, className, ...props }: FormProps): React.JSX.Element;
37
+ export declare function Form({ children, spacing, schema, shellCtx: shellCtxProp, align, gap, surface, padding, paddingX, paddingY, rounded, roundedTop, roundedBottom, border, shadow, overflow, className, ...props }: FormProps): React.JSX.Element;
29
38
  export interface FormGroupProps {
30
39
  children: React.ReactNode;
31
40
  columns?: 1 | 2 | 3 | 4;
@@ -43,12 +43,13 @@ const FORM_ALIGN_MAP = {
43
43
  end: "items-end",
44
44
  stretch: "items-stretch",
45
45
  };
46
- export function Form({ children, spacing, schema, align, gap, surface, padding, paddingX, paddingY, rounded, roundedTop, roundedBottom, border, shadow, overflow, className = "", ...props }) {
46
+ export function Form({ children, spacing, schema, shellCtx: shellCtxProp, align, gap, surface, padding, paddingX, paddingY, rounded, roundedTop, roundedBottom, border, shadow, overflow, className = "", ...props }) {
47
47
  const helpers = useFormShellState(schema);
48
+ const effectiveCtx = shellCtxProp ?? helpers.shellCtx;
48
49
  const content = typeof children === "function"
49
- ? children(helpers)
50
+ ? children({ ...helpers, shellCtx: effectiveCtx })
50
51
  : children;
51
- return (_jsx(FormShellContext.Provider, { value: helpers.shellCtx, children: _jsx("form", { className: [
52
+ return (_jsx(FormShellContext.Provider, { value: effectiveCtx, children: _jsx("form", { className: [
52
53
  "appkit-form",
53
54
  spacing ? FORM_SPACING_MAP[spacing] : "",
54
55
  align ? FORM_ALIGN_MAP[align] : "",
@@ -8,7 +8,9 @@ export interface FormFieldProps {
8
8
  value?: string;
9
9
  onChange?: (value: string) => void;
10
10
  onBlur?: () => void;
11
+ /** Override error — falls back to FormShell context error for this field */
11
12
  error?: string;
13
+ /** Override touched — falls back to FormShell context touched state for this field */
12
14
  touched?: boolean;
13
15
  placeholder?: string;
14
16
  required?: boolean;
@@ -24,5 +26,5 @@ export interface FormFieldProps {
24
26
  accept?: string;
25
27
  maxSizeMB?: number;
26
28
  }
27
- export declare function FormField({ label, name, card, type, value, onChange, onBlur, error, touched, placeholder, required, disabled, autoComplete, rows, hint, helpText, options, onUpload, captureSource, captureMode, accept, maxSizeMB, }: FormFieldProps): React.JSX.Element;
29
+ export declare function FormField({ label, name, card, type, value, onChange, onBlur, error: errorProp, touched: touchedProp, placeholder, required, disabled, autoComplete, rows, hint, helpText, options, onUpload, captureSource, captureMode, accept, maxSizeMB, }: FormFieldProps): React.JSX.Element;
28
30
  export default FormField;
@@ -1,13 +1,22 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useContext } from "react";
3
4
  import { Input } from "./Input";
4
5
  import { Select } from "./Select";
5
6
  import { Textarea } from "./Textarea";
6
7
  import { Label, Text, Span } from "./Typography";
7
8
  import { ImageUpload, MediaUploadField } from "../../features/media";
9
+ import { FormShellContext } from "../forms/FormShell";
8
10
  const CARD_CLASS = "appkit-form-field appkit-form-field--card";
9
11
  const BASE_CLASS = "appkit-form-field";
10
- export function FormField({ label, name, card = false, type = "text", value = "", onChange, onBlur, error, touched, placeholder, required = false, disabled = false, autoComplete, rows, hint, helpText, options = [], onUpload, captureSource, captureMode, accept, maxSizeMB, }) {
12
+ export function FormField({ label, name, card = false, type = "text", value = "", onChange, onBlur, error: errorProp, touched: touchedProp, placeholder, required = false, disabled = false, autoComplete, rows, hint, helpText, options = [], onUpload, captureSource, captureMode, accept, maxSizeMB, }) {
13
+ // Falls back to FormShellContext (the same context FieldInput/FieldSelect
14
+ // read) whenever the caller doesn't pass an explicit error/touched prop —
15
+ // an explicit prop always wins, preserving back-compat for the handful of
16
+ // call sites that already manage their own error state manually.
17
+ const ctx = useContext(FormShellContext);
18
+ const error = errorProp ?? ctx?.errors[name];
19
+ const touched = touchedProp ?? ctx?.touched[name];
11
20
  const showError = error
12
21
  ? touched != null
13
22
  ? touched && !!error
@@ -16,12 +25,21 @@ export function FormField({ label, name, card = false, type = "text", value = ""
16
25
  const inputId = `field-${name}`;
17
26
  const errorId = `${inputId}-error`;
18
27
  const describedBy = showError ? errorId : undefined;
28
+ function handleFieldChange(newValue) {
29
+ onChange?.(newValue);
30
+ if (showError)
31
+ ctx?.clearFieldError(name);
32
+ }
33
+ function handleFieldBlur() {
34
+ ctx?.setFieldTouched(name);
35
+ onBlur?.();
36
+ }
19
37
  if (type === "image" && onUpload) {
20
- return (_jsxs("div", { className: card ? CARD_CLASS : BASE_CLASS, "data-section": "formfield-div-505", children: [_jsx("div", { "aria-disabled": disabled || undefined, className: disabled ? "pointer-events-none opacity-60" : undefined, children: _jsx(ImageUpload, { currentImage: value || undefined, onUpload: onUpload, onChange: (url) => onChange?.(url), label: label ? `${label}${required ? " *" : ""}` : undefined, helperText: hint ?? helpText, captureSource: captureSource ?? "file-only", accept: accept, maxSizeMB: maxSizeMB }) }), showError ? (_jsx(Text, { id: errorId, size: "sm", variant: "error", className: "appkit-form-field__error", role: "alert", children: error })) : null] }));
38
+ return (_jsxs("div", { className: card ? CARD_CLASS : BASE_CLASS, "data-section": "formfield-div-505", children: [_jsx("div", { "aria-disabled": disabled || undefined, className: disabled ? "pointer-events-none opacity-60" : undefined, children: _jsx(ImageUpload, { currentImage: value || undefined, onUpload: onUpload, onChange: (url) => handleFieldChange(url), label: label ? `${label}${required ? " *" : ""}` : undefined, helperText: hint ?? helpText, captureSource: captureSource ?? "file-only", accept: accept, maxSizeMB: maxSizeMB }) }), showError ? (_jsx(Text, { id: errorId, size: "sm", variant: "error", className: "appkit-form-field__error", role: "alert", children: error })) : null] }));
21
39
  }
22
40
  if (type === "media" && onUpload) {
23
- return (_jsxs("div", { className: card ? CARD_CLASS : BASE_CLASS, "data-section": "formfield-div-506", children: [_jsx(MediaUploadField, { label: `${label || name}${required ? " *" : ""}`, value: value, onChange: (url) => onChange?.(url), onUpload: onUpload, disabled: disabled, helperText: hint ?? helpText, captureSource: captureSource ?? "file-only", captureMode: captureMode ?? "both", accept: accept, maxSizeMB: maxSizeMB }), showError ? (_jsx(Text, { id: errorId, size: "sm", variant: "error", className: "appkit-form-field__error", role: "alert", children: error })) : null] }));
41
+ return (_jsxs("div", { className: card ? CARD_CLASS : BASE_CLASS, "data-section": "formfield-div-506", children: [_jsx(MediaUploadField, { label: `${label || name}${required ? " *" : ""}`, value: value, onChange: (url) => handleFieldChange(url), onUpload: onUpload, disabled: disabled, helperText: hint ?? helpText, captureSource: captureSource ?? "file-only", captureMode: captureMode ?? "both", accept: accept, maxSizeMB: maxSizeMB }), showError ? (_jsx(Text, { id: errorId, size: "sm", variant: "error", className: "appkit-form-field__error", role: "alert", children: error })) : null] }));
24
42
  }
25
- return (_jsxs("div", { className: card ? CARD_CLASS : BASE_CLASS, "data-section": "formfield-div-507", children: [label ? (_jsxs(Label, { htmlFor: inputId, className: "appkit-form-field__label", children: [label, required ? (_jsx(Span, { className: "appkit-form-field__required", children: "*" })) : null] })) : null, type === "select" ? (_jsx(Select, { id: inputId, name: name, value: value, onChange: (event) => onChange?.(event.target.value), onBlur: onBlur, disabled: disabled, options: options, "aria-required": required || undefined, "aria-invalid": showError || undefined, "aria-describedby": describedBy })) : type === "textarea" ? (_jsx(Textarea, { id: inputId, name: name, value: value, onChange: (event) => onChange?.(event.target.value), onBlur: onBlur, placeholder: placeholder, disabled: disabled, rows: rows, "aria-required": required || undefined, "aria-invalid": showError || undefined, "aria-describedby": describedBy })) : (_jsx(Input, { id: inputId, name: name, type: type, value: value, onChange: (event) => onChange?.(event.target.value), onBlur: onBlur, placeholder: placeholder, disabled: disabled, autoComplete: autoComplete, "aria-required": required || undefined, "aria-invalid": showError || undefined, "aria-describedby": describedBy })), !showError && (hint || helpText) ? (_jsx(Text, { size: "sm", variant: "secondary", className: "appkit-form-field__hint", children: hint ?? helpText })) : null, showError ? (_jsx(Text, { id: errorId, size: "sm", variant: "error", className: "appkit-form-field__error", role: "alert", children: error })) : null] }));
43
+ return (_jsxs("div", { className: card ? CARD_CLASS : BASE_CLASS, "data-section": "formfield-div-507", children: [label ? (_jsxs(Label, { htmlFor: inputId, className: "appkit-form-field__label", children: [label, required ? (_jsx(Span, { className: "appkit-form-field__required", children: "*" })) : null] })) : null, type === "select" ? (_jsx(Select, { id: inputId, name: name, value: value, onChange: (event) => handleFieldChange(event.target.value), onBlur: handleFieldBlur, disabled: disabled, options: options, "aria-required": required || undefined, "aria-invalid": showError || undefined, "aria-describedby": describedBy })) : type === "textarea" ? (_jsx(Textarea, { id: inputId, name: name, value: value, onChange: (event) => handleFieldChange(event.target.value), onBlur: handleFieldBlur, placeholder: placeholder, disabled: disabled, rows: rows, "aria-required": required || undefined, "aria-invalid": showError || undefined, "aria-describedby": describedBy })) : (_jsx(Input, { id: inputId, name: name, type: type, value: value, onChange: (event) => handleFieldChange(event.target.value), onBlur: handleFieldBlur, placeholder: placeholder, disabled: disabled, autoComplete: autoComplete, "aria-required": required || undefined, "aria-invalid": showError || undefined, "aria-describedby": describedBy })), !showError && (hint || helpText) ? (_jsx(Text, { size: "sm", variant: "secondary", className: "appkit-form-field__hint", children: hint ?? helpText })) : null, showError ? (_jsx(Text, { id: errorId, size: "sm", variant: "error", className: "appkit-form-field__error", role: "alert", children: error })) : null] }));
26
44
  }
27
45
  export default FormField;
@@ -59,7 +59,6 @@ export interface ListingLayoutProps {
59
59
  /** Count of filter changes staged but not yet committed. Shows in Apply button. */
60
60
  filterPendingCount?: number;
61
61
  className?: string;
62
- loading?: boolean;
63
62
  errorSlot?: ReactNode;
64
63
  labels?: ListingLayoutLabels;
65
64
  /** Portal context — sets `isDashboard` default (admin/seller=true, user/public=false). */
@@ -69,4 +68,4 @@ export interface ListingLayoutProps {
69
68
  /** When provided, the entire layout is replaced with this view (detail/edit mode). */
70
69
  detailView?: ReactNode;
71
70
  }
72
- export declare function ListingLayout({ headerSlot, statusTabsSlot, filterContent, filterActiveCount, onFilterApply, onFilterClear, filterTitle, activeFiltersSlot, resultCountSlot, searchSlot, sortSlot, viewToggleSlot, actionsSlot, selectedCount, onClearSelection, bulkActionItems, toolbarPaginationSlot, paginationSlot, children, isDashboard, defaultSidebarOpen, filterPendingCount, className, loading: _loading, errorSlot, labels, portal, overlays, detailView, }: ListingLayoutProps): import("react").JSX.Element;
71
+ export declare function ListingLayout({ headerSlot, statusTabsSlot, filterContent, filterActiveCount, onFilterApply, onFilterClear, filterTitle, activeFiltersSlot, resultCountSlot, searchSlot, sortSlot, viewToggleSlot, actionsSlot, selectedCount, onClearSelection, bulkActionItems, toolbarPaginationSlot, paginationSlot, children, isDashboard, defaultSidebarOpen, filterPendingCount, className, errorSlot, labels, portal, overlays, detailView, }: ListingLayoutProps): import("react").JSX.Element;
@@ -35,7 +35,7 @@ const DEFAULT_LABELS = {
35
35
  hideFilters: "Hide filters",
36
36
  filterActiveCount: (count) => `${count} active`,
37
37
  };
38
- export function ListingLayout({ headerSlot, statusTabsSlot, filterContent, filterActiveCount = 0, onFilterApply, onFilterClear, filterTitle, activeFiltersSlot, resultCountSlot, searchSlot, sortSlot, viewToggleSlot, actionsSlot, selectedCount = 0, onClearSelection, bulkActionItems, toolbarPaginationSlot, paginationSlot, children, isDashboard, defaultSidebarOpen = false, filterPendingCount, className = "", loading: _loading = false, errorSlot, labels, portal, overlays, detailView, }) {
38
+ export function ListingLayout({ headerSlot, statusTabsSlot, filterContent, filterActiveCount = 0, onFilterApply, onFilterClear, filterTitle, activeFiltersSlot, resultCountSlot, searchSlot, sortSlot, viewToggleSlot, actionsSlot, selectedCount = 0, onClearSelection, bulkActionItems, toolbarPaginationSlot, paginationSlot, children, isDashboard, defaultSidebarOpen = false, filterPendingCount, className = "", errorSlot, labels, portal, overlays, detailView, }) {
39
39
  // useState must run unconditionally on every render — these were
40
40
  // previously declared after the detail-view early return below, which
41
41
  // skipped both hook calls whenever `detailView` was passed, violating the
@@ -0,0 +1,68 @@
1
+ /**
2
+ * RecordDetailModal — the shared "open one listing row and actually read it"
3
+ * surface.
4
+ *
5
+ * Built 2026-08-21 when `audit-listing-detail-affordance` found 16 of 70
6
+ * dashboard listing views were dead ends: rows the user could see but never
7
+ * open. Two distinct failure shapes, both fixed by this one primitive:
8
+ *
9
+ * 1. Data fetched then discarded — AdminCartsView/AdminWishlistsView/
10
+ * AdminHistoryView all compute `items.length` for the row subtitle and
11
+ * throw the `items[]` array itself away, with no surface to show it
12
+ * (CLAUDE.md Recurrent Root Cause #52).
13
+ * 2. Acting blind — AdminCatalogueApprovalsView offers Approve/Reject, and
14
+ * AdminTesterFeedbackListView offers "Confirm bug", on records whose
15
+ * content (photos, description, the tester's comment/screenshot) was
16
+ * never rendered anywhere.
17
+ *
18
+ * Sixteen bespoke modals would have tripped the Duplication Decision
19
+ * Framework's Rule of Three immediately, so this is deliberately one
20
+ * primitive with three optional content slots — `fields` (definition rows),
21
+ * `items` (a thumbnail list, the Root Cause #52 fix), and `metadata` (a raw
22
+ * JSON payload dump) — rather than a per-feature component each time.
23
+ *
24
+ * Consumers pass plain data, not JSX, so a listing view's detail wiring stays
25
+ * the ~20-line "map the raw row into fields/items" shape at every call site.
26
+ */
27
+ import type { ReactNode } from "react";
28
+ import type { JsonValue } from "../../schemas/types";
29
+ export interface RecordDetailField {
30
+ label: string;
31
+ /** Rendered as-is when a node; plain strings/numbers get default styling. */
32
+ value: ReactNode;
33
+ }
34
+ export interface RecordDetailItem {
35
+ /** Thumbnail URL — falls back to MediaImage's placeholder when absent. */
36
+ image?: string;
37
+ title: string;
38
+ subtitle?: string;
39
+ /** Right-aligned trailing text (price, quantity, timestamp). */
40
+ trailing?: string;
41
+ /** When set, the title becomes a link to this record's own detail page. */
42
+ href?: string;
43
+ }
44
+ export interface RecordDetailBadge {
45
+ label: string;
46
+ variant?: "default" | "success" | "warning" | "danger" | "info" | "secondary";
47
+ }
48
+ export interface RecordDetailModalProps {
49
+ isOpen: boolean;
50
+ onClose: () => void;
51
+ title: string;
52
+ /** Small status chips under the title (status, read-state, role, etc). */
53
+ badges?: RecordDetailBadge[];
54
+ /** Free-text lead paragraph — a comment body, description, or message. */
55
+ description?: string;
56
+ /** Definition rows: the record's scalar fields. */
57
+ fields?: RecordDetailField[];
58
+ /** A nested collection on the record (cart items, wishlist entries, …). */
59
+ items?: {
60
+ heading: string;
61
+ entries: RecordDetailItem[];
62
+ };
63
+ /** Raw payload dump for records that carry an open-ended object. */
64
+ metadata?: Record<string, JsonValue>;
65
+ /** Action buttons rendered at the bottom (approve/reject/etc). */
66
+ footer?: ReactNode;
67
+ }
68
+ export declare function RecordDetailModal({ isOpen, onClose, title, badges, description, fields, items, metadata, footer, }: RecordDetailModalProps): import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Anchor, Badge, Code, Div, Modal, Row, Stack, Text } from "../index";
4
+ import { MediaImage } from "../../features/media/MediaImage";
5
+ export function RecordDetailModal({ isOpen, onClose, title, badges, description, fields, items, metadata, footer, }) {
6
+ const hasMetadata = Boolean(metadata && Object.keys(metadata).length > 0);
7
+ return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, title: title, children: _jsxs(Stack, { gap: "md", children: [badges && badges.length > 0 && (_jsx(Row, { gap: "sm", align: "center", wrap: true, children: badges.map((b) => (_jsx(Badge, { variant: b.variant ?? "default", children: b.label }, b.label))) })), description && (_jsx(Div, { rounded: "lg", padding: "sm", surface: "muted", border: "default", children: _jsx(Text, { size: "sm", children: description }) })), fields && fields.length > 0 && (_jsx(Stack, { gap: "xs", children: fields.map((f) => (_jsxs(Row, { justify: "between", gap: "md", children: [_jsx(Text, { size: "sm", color: "muted", children: f.label }), _jsx(Text, { size: "sm", weight: "medium", className: "text-right", children: f.value })] }, f.label))) })), items && items.entries.length > 0 && (_jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { size: "xs", weight: "semibold", color: "muted", children: [items.heading, " (", items.entries.length, ")"] }), _jsx(Div, { className: "divide-y divide-[var(--appkit-color-border)] border border-[var(--appkit-color-border)]", rounded: "lg", children: items.entries.map((entry, i) => (_jsxs(Row, { gap: "sm", align: "center", justify: "between", paddingY: "y-xs-tall", padding: "x-sm", children: [_jsxs(Row, { gap: "sm", align: "center", className: "min-w-0", children: [_jsx(Div, { className: "h-10 w-10 shrink-0", rounded: "md", overflow: "hidden", children: _jsx(MediaImage, { src: entry.image, alt: entry.title, size: "thumbnail" }) }), _jsxs(Stack, { gap: "none", className: "min-w-0", children: [entry.href ? (_jsx(Anchor, { href: entry.href, tone: "brand", size: "sm", weight: "medium", children: entry.title })) : (_jsx(Text, { size: "sm", weight: "medium", className: "truncate", children: entry.title })), entry.subtitle && (_jsx(Text, { size: "xs", color: "muted", children: entry.subtitle }))] })] }), entry.trailing && (_jsx(Text, { size: "sm", weight: "medium", className: "shrink-0", children: entry.trailing }))] }, `${entry.title}-${i}`))) })] })), hasMetadata && (_jsxs(Div, { rounded: "lg", padding: "sm", surface: "muted", border: "default", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", className: "mb-1", children: "Details" }), _jsx(Code, { size: "xs", className: "block whitespace-pre-wrap font-mono", children: JSON.stringify(metadata, null, 2) })] })), footer && _jsx(Row, { justify: "end", gap: "sm", children: footer })] }) }));
8
+ }
@@ -25,7 +25,15 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
25
25
  import { useState } from "react";
26
26
  import { Div } from "./Div";
27
27
  import { Heading } from "./Typography";
28
- export function SlottedListingView({ title, labels = {}, portal, renderHeader, renderTabs, renderSearch, renderSort, renderFilters, renderActiveFilters, renderBulkActions, renderTable, renderPagination, overlays, manageSearch: _manageSearch, manageSelection, manageSort: _manageSort = false, inlineToolbar = false, total = 0, isLoading = false, className = "", }) {
28
+ export function SlottedListingView({ title, labels = {}, portal, renderHeader, renderTabs, renderSearch, renderSort, renderFilters, renderActiveFilters, renderBulkActions, renderTable, renderPagination, overlays,
29
+ // audit-dead-underscore-prop-ok: deliberately unused — see the doc comment
30
+ // on `manageSearch` above; wiring this to gate renderSearch would break
31
+ // real consumers with self-contained closures.
32
+ manageSearch: _manageSearch, manageSelection,
33
+ // audit-dead-underscore-prop-ok: deliberately unused — see the doc comment
34
+ // on `manageSort` above; wiring this to gate renderSort would break real
35
+ // consumers with self-contained closures.
36
+ manageSort: _manageSort = false, inlineToolbar = false, total = 0, isLoading = false, className = "", }) {
29
37
  const effectiveManageSelection = manageSelection ?? (portal === "admin" || portal === "seller");
30
38
  const [search, setSearch] = useState("");
31
39
  const [selectedIds, setSelectedIds] = useState([]);
@@ -0,0 +1,25 @@
1
+ export interface FormErrorSummaryProps {
2
+ /** Hide the whole block when there are currently no errors. Default true. */
3
+ hideWhenEmpty?: boolean;
4
+ className?: string;
5
+ /** Heading shown above the error list. */
6
+ title?: string;
7
+ }
8
+ /**
9
+ * Shared, live validation-error summary — supplements (does not replace) the
10
+ * per-field inline errors `FieldInput`/`FieldSelect`/`FormField` already
11
+ * render. Reads `FormShellContext` directly (no required props), so it's
12
+ * placed once beside a form's Submit/Save button and reflects every current
13
+ * error immediately as fields change — not gated by `touched`, unlike the
14
+ * inline per-field errors.
15
+ *
16
+ * For multi-step forms whose step engine populates
17
+ * `FormShellContextValue.fieldToStepIndex`, each entry names its owning step
18
+ * and is clickable to jump there. Falls back to a flat list when no step
19
+ * context exists (single-step `<Form>` usage).
20
+ *
21
+ * Uses the `.appkit-formshell__error-summary` class family already defined
22
+ * in `FormShell.style.css` for this exact purpose.
23
+ */
24
+ export declare function FormErrorSummary({ hideWhenEmpty, className, title, }: FormErrorSummaryProps): import("react").JSX.Element | null;
25
+ export default FormErrorSummary;
@@ -0,0 +1,37 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useContext } from "react";
4
+ import { FormShellContext } from "./FormShell";
5
+ import { Span } from "../components/Typography";
6
+ /**
7
+ * Shared, live validation-error summary — supplements (does not replace) the
8
+ * per-field inline errors `FieldInput`/`FieldSelect`/`FormField` already
9
+ * render. Reads `FormShellContext` directly (no required props), so it's
10
+ * placed once beside a form's Submit/Save button and reflects every current
11
+ * error immediately as fields change — not gated by `touched`, unlike the
12
+ * inline per-field errors.
13
+ *
14
+ * For multi-step forms whose step engine populates
15
+ * `FormShellContextValue.fieldToStepIndex`, each entry names its owning step
16
+ * and is clickable to jump there. Falls back to a flat list when no step
17
+ * context exists (single-step `<Form>` usage).
18
+ *
19
+ * Uses the `.appkit-formshell__error-summary` class family already defined
20
+ * in `FormShell.style.css` for this exact purpose.
21
+ */
22
+ export function FormErrorSummary({ hideWhenEmpty = true, className, title = "Please fix the following:", }) {
23
+ const ctx = useContext(FormShellContext);
24
+ if (!ctx)
25
+ return null;
26
+ const entries = Object.entries(ctx.errors);
27
+ if (entries.length === 0 && hideWhenEmpty)
28
+ return null;
29
+ if (entries.length === 0)
30
+ return null;
31
+ return (_jsxs("div", { role: "alert", "aria-live": "polite", className: ["appkit-formshell__error-summary", className].filter(Boolean).join(" "), children: [_jsx("div", { className: "appkit-formshell__error-summary__title", children: title }), _jsx("ul", { className: "appkit-formshell__error-summary__list", children: entries.map(([key, message]) => {
32
+ const stepIndex = ctx.fieldToStepIndex?.[key];
33
+ const stepLabel = stepIndex != null ? ctx.steps[stepIndex]?.label : undefined;
34
+ return (_jsx("li", { children: stepLabel ? (_jsxs("button", { type: "button", className: "appkit-formshell__error-summary__step-link", onClick: () => ctx.goToStep(stepIndex), children: [_jsxs(Span, { weight: "semibold", children: [stepLabel, ":"] }), " ", message] })) : (_jsx(Span, { children: message })) }, key));
35
+ }) })] }));
36
+ }
37
+ export default FormErrorSummary;
@@ -30,6 +30,14 @@ export interface FormShellContextValue {
30
30
  isDirty: boolean;
31
31
  isSubmitting: boolean;
32
32
  stepErrorCounts: number[];
33
+ /**
34
+ * Maps an error key (same key shape `errors` uses) to the index into
35
+ * `steps` that owns it. Populated only by step-aware producers (the
36
+ * `features/shell` wizard chrome/step engine) — undefined for single-step
37
+ * forms (`<Form>`, `useFormShellState` with no steps). `<FormErrorSummary>`
38
+ * treats undefined as "no step context" and renders a flat list.
39
+ */
40
+ fieldToStepIndex?: Record<string, number>;
33
41
  }
34
42
  export declare const FormShellContext: React.Context<FormShellContextValue | null>;
35
43
  export declare function useFormShell(): FormShellContextValue;
@@ -43,12 +51,22 @@ export interface UseFormShellStateResult {
43
51
  * supplied values, write inline errors via `setFieldError`, and return the
44
52
  * parsed value on success or null on failure.
45
53
  */
46
- validate: <T = unknown>(values: FormValues) => T | null;
54
+ validate: <T = unknown>(values: unknown) => T | null;
47
55
  }
48
56
  /**
49
57
  * `applyZodIssues` — pipe every issue from a Zod safeParse failure into the
50
58
  * FormShellContext error map. Use after `schema.safeParse(values)` returns
51
59
  * `success: false` to surface inline errors on `<FieldInput>` etc.
60
+ *
61
+ * Keys are the full dotted/indexed path (`"video.duration"`, `"images.2"`),
62
+ * not just the top-level segment — a plain `issue.path[0]` key would collapse
63
+ * every nested issue under one object onto the same map entry, silently
64
+ * dropping all but the last. Top-level-only fields (the common case) are
65
+ * unaffected: a single-segment path joins to the same string as before.
66
+ *
67
+ * Root-level `.refine()` issues (empty `path: []`, no owning field) are still
68
+ * skipped — there is no field to attach them to, and no schema in this
69
+ * codebase currently relies on one being surfaced this way.
52
70
  */
53
71
  export declare function applyZodIssues(issues: ReadonlyArray<{
54
72
  path: ReadonlyArray<PropertyKey>;
@@ -70,5 +88,22 @@ export interface FormShellProviderProps {
70
88
  autoSaveDelayMs?: number;
71
89
  /** Current form values — passed into onSaveDraft on auto-save */
72
90
  values?: FormValues;
91
+ /**
92
+ * Optional Zod schema — when supplied, the provider runs `.safeParse(values)`
93
+ * live (every time `values` changes, not just on submit) and populates its
94
+ * own `errors` map from the issues, so `<FieldInput>`/`<FormField>`/
95
+ * `<FormErrorSummary>` descendants get real validation without the parent
96
+ * component having to reach into the provider's internal state itself.
97
+ */
98
+ schema?: import("zod").ZodTypeAny;
99
+ /**
100
+ * Maps an error key to the index of the step that owns it — forwarded
101
+ * as-is into the context value for `<FormErrorSummary>` to consume. The
102
+ * provider itself has no concept of "steps"; the caller (typically a
103
+ * step-wizard shell) computes this from its own step definitions.
104
+ */
105
+ fieldToStepIndex?: Record<string, number>;
106
+ /** Wired into `FormShellContextValue.goToStep` when the caller has real step navigation to drive (e.g. a step-wizard's `setCurrentStep`). */
107
+ onGoToStep?: (n: number) => void;
73
108
  }
74
- export declare function FormShellProvider({ children, onSaveDraft, isDirty, autoSaveDelayMs, values, }: FormShellProviderProps): React.JSX.Element;
109
+ export declare function FormShellProvider({ children, onSaveDraft, isDirty, autoSaveDelayMs, values, schema, fieldToStepIndex, onGoToStep, }: FormShellProviderProps): React.JSX.Element;
@@ -13,13 +13,23 @@ export function useFormShell() {
13
13
  * `applyZodIssues` — pipe every issue from a Zod safeParse failure into the
14
14
  * FormShellContext error map. Use after `schema.safeParse(values)` returns
15
15
  * `success: false` to surface inline errors on `<FieldInput>` etc.
16
+ *
17
+ * Keys are the full dotted/indexed path (`"video.duration"`, `"images.2"`),
18
+ * not just the top-level segment — a plain `issue.path[0]` key would collapse
19
+ * every nested issue under one object onto the same map entry, silently
20
+ * dropping all but the last. Top-level-only fields (the common case) are
21
+ * unaffected: a single-segment path joins to the same string as before.
22
+ *
23
+ * Root-level `.refine()` issues (empty `path: []`, no owning field) are still
24
+ * skipped — there is no field to attach them to, and no schema in this
25
+ * codebase currently relies on one being surfaced this way.
16
26
  */
17
27
  export function applyZodIssues(issues, setFieldError) {
18
28
  for (const issue of issues) {
19
- const key = issue.path[0];
20
- if (key == null)
29
+ if (!issue.path || issue.path.length === 0)
21
30
  continue;
22
- setFieldError(String(key), issue.message);
31
+ const key = issue.path.map(String).join(".");
32
+ setFieldError(key, issue.message);
23
33
  }
24
34
  }
25
35
  /**
@@ -72,7 +82,7 @@ export function useFormShellState(schema) {
72
82
  }), [errors, setFieldError]);
73
83
  return { shellCtx, setFieldError, clearErrors, hasErrors: Object.keys(errors).length > 0, validate };
74
84
  }
75
- export function FormShellProvider({ children, onSaveDraft, isDirty = false, autoSaveDelayMs = 2000, values = {}, }) {
85
+ export function FormShellProvider({ children, onSaveDraft, isDirty = false, autoSaveDelayMs = 2000, values = {}, schema, fieldToStepIndex, onGoToStep, }) {
76
86
  const [errors, setErrors] = useState({});
77
87
  const [touched, setTouched] = useState({});
78
88
  const autoSaveTimer = useRef(null);
@@ -89,6 +99,25 @@ export function FormShellProvider({ children, onSaveDraft, isDirty = false, auto
89
99
  return () => { if (autoSaveTimer.current)
90
100
  clearTimeout(autoSaveTimer.current); };
91
101
  }, [values, isDirty, onSaveDraft, autoSaveDelayMs]);
102
+ useEffect(() => {
103
+ if (!schema)
104
+ return;
105
+ // audit-unvalidated-safeparse-ok: bulk-replaces the whole errors map from
106
+ // the full parse result (so a field that just became valid is cleared),
107
+ // which applyZodIssues (incremental setFieldError-per-issue) can't do.
108
+ const parsed = schema.safeParse(values);
109
+ if (parsed.success) {
110
+ setErrors({});
111
+ return;
112
+ }
113
+ const next = {};
114
+ for (const issue of parsed.error.issues) {
115
+ if (!issue.path || issue.path.length === 0)
116
+ continue;
117
+ next[issue.path.map(String).join(".")] = issue.message;
118
+ }
119
+ setErrors(next);
120
+ }, [schema, values]);
92
121
  const setFieldError = useCallback((name, error) => {
93
122
  setErrors((prev) => {
94
123
  if (!error) {
@@ -121,13 +150,14 @@ export function FormShellProvider({ children, onSaveDraft, isDirty = false, auto
121
150
  clearFieldError,
122
151
  steps: [],
123
152
  currentStep: 0,
124
- goToStep: () => { },
153
+ goToStep: onGoToStep ?? (() => { }),
125
154
  nextStep: () => { },
126
155
  prevStep: () => { },
127
- isPublishReady: true,
156
+ isPublishReady: Object.keys(errors).length === 0,
128
157
  isDirty,
129
158
  isSubmitting: false,
130
159
  stepErrorCounts: [],
131
- }), [errors, touched, setFieldError, setFieldTouched, clearFieldError, isDirty]);
160
+ fieldToStepIndex,
161
+ }), [errors, touched, setFieldError, setFieldTouched, clearFieldError, isDirty, onGoToStep, fieldToStepIndex]);
132
162
  return _jsx(FormShellContext.Provider, { value: ctx, children: children });
133
163
  }
@@ -142,6 +142,23 @@
142
142
  gap: var(--appkit-spacing-1, 0.25rem);
143
143
  }
144
144
 
145
+ .appkit-formshell__error-summary__step-link {
146
+ background: none;
147
+ border: none;
148
+ padding: 0;
149
+ margin: 0;
150
+ font: inherit;
151
+ color: inherit;
152
+ text-align: left;
153
+ cursor: pointer;
154
+ text-decoration: underline;
155
+ text-underline-offset: 2px;
156
+ }
157
+
158
+ .appkit-formshell__error-summary__step-link:hover {
159
+ color: var(--appkit-color-error, #dc2626);
160
+ }
161
+
145
162
  /* ─── Body + Steps ───────────────────────────────────────────────────────── */
146
163
 
147
164
  .appkit-formshell__body {
@@ -10,3 +10,5 @@ export type { FieldTextareaProps } from "./FieldTextarea";
10
10
  export { FieldTextarea } from "./FieldTextarea";
11
11
  export type { ColorPickerFieldProps } from "./ColorPickerField";
12
12
  export { ColorPickerField } from "./ColorPickerField";
13
+ export type { FormErrorSummaryProps } from "./FormErrorSummary";
14
+ export { FormErrorSummary } from "./FormErrorSummary";
@@ -4,3 +4,4 @@ export { FieldSelect } from "./FieldSelect";
4
4
  export { FieldCheckbox } from "./FieldCheckbox";
5
5
  export { FieldTextarea } from "./FieldTextarea";
6
6
  export { ColorPickerField } from "./ColorPickerField";
7
+ export { FormErrorSummary } from "./FormErrorSummary";
@@ -10,6 +10,8 @@ export type { FieldCheckboxProps } from "./forms";
10
10
  export { FieldCheckbox } from "./forms";
11
11
  export type { FieldTextareaProps } from "./forms";
12
12
  export { FieldTextarea } from "./forms";
13
+ export type { FormErrorSummaryProps } from "./forms";
14
+ export { FormErrorSummary } from "./forms";
13
15
  export { buildColumns, createColumnBuilder, renderBoolean, renderCurrency, renderCurrencyCompact, renderCount, renderNullable, renderRating, getStatusTone, getResourceIcon, } from "./columns";
14
16
  export type { BooleanRenderOpts, RatingMode, RatingRenderOpts, StatusTone, } from "./columns";
15
17
  export type { SectionProps, SectionBackgroundConfig, ArticleProps, MainProps, AsideProps, NavProps, BlockHeaderProps, BlockFooterProps, UlProps, OlProps, LiProps, TableProps, TheadProps, TbodyProps, TrProps, ThProps, TdProps, CodeProps, PreProps, BlockquoteProps, FigureProps, FigcaptionProps, DlProps, DtProps, DdProps, } from "./components/Semantic";
@@ -229,3 +231,5 @@ export { Show, Hide } from "./components/Responsive";
229
231
  export type { ShowProps, HideProps } from "./components/Responsive";
230
232
  export { CollapsibleSection } from "./components/CollapsibleSection";
231
233
  export type { CollapsibleSectionProps } from "./components/CollapsibleSection";
234
+ export { RecordDetailModal } from "./components/RecordDetailModal";
235
+ export type { RecordDetailModalProps, RecordDetailField, RecordDetailItem, RecordDetailBadge, } from "./components/RecordDetailModal";
package/dist/ui/index.js CHANGED
@@ -5,6 +5,7 @@ export { FieldInput } from "./forms";
5
5
  export { FieldSelect } from "./forms";
6
6
  export { FieldCheckbox } from "./forms";
7
7
  export { FieldTextarea } from "./forms";
8
+ export { FormErrorSummary } from "./forms";
8
9
  // --- Column utilities ---------------------------------------------------------
9
10
  export { buildColumns, createColumnBuilder, renderBoolean, renderCurrency, renderCurrencyCompact, renderCount, renderNullable, renderRating, getStatusTone, getResourceIcon, } from "./columns";
10
11
  export { Section, sectionBackgroundStyle, Article, Main, Aside, Nav, BlockHeader, BlockFooter, Ul, Ol, Li, Table, Thead, Tbody, Tr, Th, Td, Code, Pre, Blockquote, Figure, Figcaption, Dl, Dt, Dd, } from "./components/Semantic";
@@ -120,3 +121,7 @@ export { Kbd } from "./components/Kbd";
120
121
  export { StickyToolbar } from "./components/StickyToolbar";
121
122
  export { Show, Hide } from "./components/Responsive";
122
123
  export { CollapsibleSection } from "./components/CollapsibleSection";
124
+ // RecordDetailModal — shared "open one listing row and read it" surface.
125
+ // Backs the 2026-08-21 dead-end-listing sweep; see the component's own
126
+ // header comment and scripts/audit-listing-detail-affordance.mjs.
127
+ export { RecordDetailModal } from "./components/RecordDetailModal";
@@ -10,9 +10,11 @@ export declare const GCS_HOST = "storage.googleapis.com";
10
10
  /**
11
11
  * Normalise any image URL so it goes through the watermark proxy:
12
12
  * - /media/<path> → return as-is (already proxied via Firebase Storage)
13
+ * - blob: / data: URI → return as-is (local-only, never fetchable server-side)
13
14
  * - Firebase Storage URL → extract /o/ path → /media/<path>
15
+ * - GCS (storage.googleapis.com) URL → drop the bucket segment → /media/<path>
14
16
  * - Any other absolute URL → /api/media/ext?url=<encoded> (ext watermark proxy)
15
- * - Relative / data: URI → return as-is
17
+ * - Relative URI → return as-is
16
18
  * - Falsy → undefined
17
19
  */
18
20
  export declare function resolveMediaUrl(url: string | null | undefined): string | undefined;
@@ -13,9 +13,11 @@ const PROXY_PREFIX = "/media/";
13
13
  /**
14
14
  * Normalise any image URL so it goes through the watermark proxy:
15
15
  * - /media/<path> → return as-is (already proxied via Firebase Storage)
16
+ * - blob: / data: URI → return as-is (local-only, never fetchable server-side)
16
17
  * - Firebase Storage URL → extract /o/ path → /media/<path>
18
+ * - GCS (storage.googleapis.com) URL → drop the bucket segment → /media/<path>
17
19
  * - Any other absolute URL → /api/media/ext?url=<encoded> (ext watermark proxy)
18
- * - Relative / data: URI → return as-is
20
+ * - Relative URI → return as-is
19
21
  * - Falsy → undefined
20
22
  */
21
23
  export function resolveMediaUrl(url) {
@@ -23,6 +25,13 @@ export function resolveMediaUrl(url) {
23
25
  return undefined;
24
26
  if (url.startsWith(PROXY_PREFIX))
25
27
  return url;
28
+ // A blob:/data: URI is only ever valid in the tab that created it (e.g. a
29
+ // freshly-selected file preview via URL.createObjectURL, or a FileReader
30
+ // data URL fed to a crop modal). Routing it through the external-URL
31
+ // watermark proxy would try to fetch it server-side and 400 — it must be
32
+ // rendered directly instead.
33
+ if (url.startsWith("blob:") || url.startsWith("data:"))
34
+ return url;
26
35
  try {
27
36
  const parsed = new URL(url);
28
37
  if (parsed.hostname.endsWith(FIREBASE_STORAGE_HOST)) {
@@ -30,6 +39,16 @@ export function resolveMediaUrl(url) {
30
39
  if (m)
31
40
  return `${PROXY_PREFIX}${decodeURIComponent(m[1])}`;
32
41
  }
42
+ if (parsed.hostname === GCS_HOST) {
43
+ // Path shape: /<bucket>/<object-path...> — drop the bucket segment,
44
+ // proxy the rest like a Firebase Storage URL (consistent watermarking
45
+ // + caching instead of falling through to the external-URL proxy).
46
+ const parts = parsed.pathname.replace(/^\//, "").split("/");
47
+ if (parts.length > 1) {
48
+ const objectPath = parts.slice(1).join("/");
49
+ return `${PROXY_PREFIX}${decodeURIComponent(objectPath)}`;
50
+ }
51
+ }
33
52
  return MEDIA_ENDPOINTS.EXT_URL(url);
34
53
  }
35
54
  catch (_err) {
@@ -114,17 +114,17 @@ export declare const changePasswordSchema: z.ZodEffects<z.ZodObject<{
114
114
  currentPassword: z.ZodString;
115
115
  newPassword: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
116
116
  }, "strip", z.ZodTypeAny, {
117
- currentPassword: string;
118
117
  newPassword: string;
119
- }, {
120
118
  currentPassword: string;
119
+ }, {
121
120
  newPassword: string;
122
- }>, {
123
121
  currentPassword: string;
122
+ }>, {
124
123
  newPassword: string;
125
- }, {
126
124
  currentPassword: string;
125
+ }, {
127
126
  newPassword: string;
127
+ currentPassword: string;
128
128
  }>;
129
129
  export declare const cropDataSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodObject<{
130
130
  sourceUrl: z.ZodEffects<z.ZodString, string, string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mohasinac/appkit",
3
- "version": "4.10.0",
3
+ "version": "4.11.0",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"