@mohasinac/appkit 4.10.0 → 4.11.1

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 (324) 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/features/products/data.d.ts +2 -2
  19. package/dist/_internal/server/features/products/data.js +5 -23
  20. package/dist/_internal/server/functions/scheduled.d.ts +3 -1
  21. package/dist/_internal/server/functions/scheduled.js +17 -1
  22. package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +40 -0
  23. package/dist/_internal/server/jobs/core/dailyStatusDigest.js +164 -0
  24. package/dist/_internal/server/jobs/core/hardBanCascade.js +11 -0
  25. package/dist/_internal/server/jobs/core/testerSandboxCleanup.d.ts +1 -0
  26. package/dist/_internal/server/jobs/core/testerSandboxCleanup.js +4 -1
  27. package/dist/_internal/server/jobs/core/testerSandboxRefresh.d.ts +12 -0
  28. package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +85 -0
  29. package/dist/_internal/server/jobs/core/whatsappNotify.js +16 -0
  30. package/dist/_internal/server/jobs/handlers/dailyStatusDigest.d.ts +2 -0
  31. package/dist/_internal/server/jobs/handlers/dailyStatusDigest.js +4 -0
  32. package/dist/_internal/server/jobs/handlers/index.d.ts +2 -0
  33. package/dist/_internal/server/jobs/handlers/index.js +3 -0
  34. package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.d.ts +2 -0
  35. package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.js +2 -0
  36. package/dist/_internal/server/notifications/channel-health.d.ts +27 -0
  37. package/dist/_internal/server/notifications/channel-health.js +101 -0
  38. package/dist/_internal/shared/features/brands/schema.d.ts +0 -6
  39. package/dist/_internal/shared/features/brands/schema.js +0 -1
  40. package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +7 -0
  41. package/dist/_internal/shared/features/categories/bundle-schemas.js +3 -0
  42. package/dist/_internal/shared/features/orders/schema.d.ts +8 -8
  43. package/dist/_internal/shared/features/products/to-product-item.d.ts +25 -0
  44. package/dist/_internal/shared/features/products/to-product-item.js +45 -0
  45. package/dist/client.d.ts +21 -6
  46. package/dist/client.js +13 -3
  47. package/dist/constants/api-endpoints.d.ts +6 -9
  48. package/dist/constants/api-endpoints.js +2 -3
  49. package/dist/contracts/client-auth.d.ts +3 -7
  50. package/dist/contracts/email.d.ts +1 -0
  51. package/dist/features/account/components/AddressForm.js +24 -2
  52. package/dist/features/account/components/BecomeSellerView.d.ts +1 -2
  53. package/dist/features/account/components/BecomeSellerView.js +1 -1
  54. package/dist/features/account/components/NotificationBell.js +1 -1
  55. package/dist/features/account/components/UserBidsView.d.ts +1 -0
  56. package/dist/features/account/components/UserBidsView.js +47 -0
  57. package/dist/features/account/components/UserOrdersView.d.ts +4 -11
  58. package/dist/features/account/components/UserOrdersView.js +54 -4
  59. package/dist/features/account/components/UserReturnsView.d.ts +4 -9
  60. package/dist/features/account/components/UserReturnsView.js +37 -3
  61. package/dist/features/account/components/index.d.ts +3 -2
  62. package/dist/features/account/components/index.js +1 -0
  63. package/dist/features/admin/actions/admin-actions.js +22 -0
  64. package/dist/features/admin/actions/admin-coupon-actions.js +10 -0
  65. package/dist/features/admin/actions/notification-actions.js +4 -3
  66. package/dist/features/admin/components/AdminAdsView.d.ts +2 -3
  67. package/dist/features/admin/components/AdminAdsView.js +86 -89
  68. package/dist/features/admin/components/AdminAllEventEntriesView.js +18 -3
  69. package/dist/features/admin/components/AdminAuditLogView.d.ts +11 -0
  70. package/dist/features/admin/components/AdminAuditLogView.js +95 -0
  71. package/dist/features/admin/components/AdminBlogEditorView.js +37 -3
  72. package/dist/features/admin/components/AdminBrandEditorView.js +10 -6
  73. package/dist/features/admin/components/AdminBundleEditorView.js +18 -2
  74. package/dist/features/admin/components/AdminCarouselEditorView.js +2 -2
  75. package/dist/features/admin/components/AdminCarouselGroupEditorView.js +2 -2
  76. package/dist/features/admin/components/AdminCartsView.js +34 -4
  77. package/dist/features/admin/components/AdminCategoryEditorView.js +5 -4
  78. package/dist/features/admin/components/AdminCouponEditorView.js +2 -2
  79. package/dist/features/admin/components/AdminFaqEditorView.js +2 -2
  80. package/dist/features/admin/components/AdminHistoryView.js +11 -1
  81. package/dist/features/admin/components/AdminNotificationsView.js +27 -1
  82. package/dist/features/admin/components/AdminOrderEditorView.d.ts +10 -1
  83. package/dist/features/admin/components/AdminOrderEditorView.js +4 -3
  84. package/dist/features/admin/components/AdminOrdersView.js +92 -24
  85. package/dist/features/admin/components/AdminPayoutMarkPaidModal.d.ts +7 -0
  86. package/dist/features/admin/components/AdminPayoutMarkPaidModal.js +47 -0
  87. package/dist/features/admin/components/AdminPayoutsView.js +15 -31
  88. package/dist/features/admin/components/AdminProductEditorView.js +26 -3
  89. package/dist/features/admin/components/AdminSiteSettingsView.js +20 -1
  90. package/dist/features/admin/components/AdminStoreAddressesView.js +22 -2
  91. package/dist/features/admin/components/AdminStoresView.js +7 -0
  92. package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +2 -2
  93. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +2 -2
  94. package/dist/features/admin/components/AdminUserEditorView.d.ts +2 -2
  95. package/dist/features/admin/components/AdminUserEditorView.js +4 -2
  96. package/dist/features/admin/components/AdminUsersView.js +1 -1
  97. package/dist/features/admin/components/AdminWishlistsView.js +14 -1
  98. package/dist/features/admin/components/DataListingView.d.ts +2 -2
  99. package/dist/features/admin/components/ViewAuditLogEntryModal.d.ts +19 -0
  100. package/dist/features/admin/components/ViewAuditLogEntryModal.js +22 -0
  101. package/dist/features/admin/components/ViewNotificationModal.d.ts +21 -0
  102. package/dist/features/admin/components/ViewNotificationModal.js +15 -0
  103. package/dist/features/admin/components/analytics/AdminPageViewsReportView.d.ts +4 -7
  104. package/dist/features/admin/components/analytics/AdminPageViewsReportView.js +56 -36
  105. package/dist/features/admin/components/index.d.ts +8 -1
  106. package/dist/features/admin/components/index.js +4 -0
  107. package/dist/features/admin/constants/filter-tabs.d.ts +49 -0
  108. package/dist/features/admin/constants/filter-tabs.js +27 -0
  109. package/dist/features/admin/hooks/useAdminListing.d.ts +6 -0
  110. package/dist/features/admin/hooks/useAdminListing.js +3 -1
  111. package/dist/features/admin/hooks/useAdminListingData.d.ts +9 -1
  112. package/dist/features/admin/hooks/useAdminListingData.js +9 -2
  113. package/dist/features/admin/repository/site-settings.repository.d.ts +5 -0
  114. package/dist/features/admin/schemas/firestore.d.ts +12 -0
  115. package/dist/features/analytics/types.d.ts +1 -1
  116. package/dist/features/analytics/types.js +8 -0
  117. package/dist/features/auctions/schemas/index.d.ts +5 -0
  118. package/dist/features/audit-log/repository/audit-log.repository.d.ts +45 -0
  119. package/dist/features/audit-log/repository/audit-log.repository.js +30 -0
  120. package/dist/features/audit-log/schemas/firestore.d.ts +59 -0
  121. package/dist/features/audit-log/schemas/firestore.js +35 -0
  122. package/dist/features/auth/actions/index.d.ts +0 -1
  123. package/dist/features/auth/actions/index.js +0 -1
  124. package/dist/features/auth/hooks/useAuth.d.ts +8 -35
  125. package/dist/features/auth/hooks/useAuth.js +21 -37
  126. package/dist/features/auth/permissions/constants.d.ts +1 -1
  127. package/dist/features/auth/permissions/constants.js +2 -0
  128. package/dist/features/blog/schemas/index.d.ts +32 -0
  129. package/dist/features/catalogue/components/AdminCatalogueApprovalsView.js +31 -3
  130. package/dist/features/catalogue/components/CatalogueItemEditorView.js +2 -1
  131. package/dist/features/categories/components/BrandDetailPageView.js +14 -12
  132. package/dist/features/categories/components/CategoryDetailPageView.js +7 -3
  133. package/dist/features/categories/components/CategoryHighlightsAndFaqSection.d.ts +14 -0
  134. package/dist/features/categories/components/CategoryHighlightsAndFaqSection.js +22 -0
  135. package/dist/features/categories/components/CategoryProductsView.d.ts +1 -5
  136. package/dist/features/categories/components/CategoryProductsView.js +1 -1
  137. package/dist/features/categories/components/CategorySelectorCreate.js +16 -2
  138. package/dist/features/categories/schemas/firestore.d.ts +19 -2
  139. package/dist/features/categories/schemas/index.d.ts +24 -3
  140. package/dist/features/categories/schemas/index.js +3 -1
  141. package/dist/features/categories/types/index.d.ts +7 -0
  142. package/dist/features/checkout/actions/checkout-value-otp-actions.d.ts +17 -3
  143. package/dist/features/checkout/actions/checkout-value-otp-actions.js +101 -22
  144. package/dist/features/contact/components/ContactForm.js +17 -4
  145. package/dist/features/contact/email.d.ts +1 -0
  146. package/dist/features/contact/email.js +3 -0
  147. package/dist/features/events/components/AdminEventEditorView.js +54 -2
  148. package/dist/features/events/components/AdminEventEntriesView.d.ts +2 -16
  149. package/dist/features/events/components/AdminEventEntriesView.js +56 -89
  150. package/dist/features/events/components/EventFormDrawer.js +5 -1
  151. package/dist/features/events/components/EventRaffleEntryForm.d.ts +1 -3
  152. package/dist/features/events/components/EventRaffleEntryForm.js +1 -1
  153. package/dist/features/events/schemas/index.d.ts +22 -0
  154. package/dist/features/grouped/components/GroupedListingsCarousel.js +4 -1
  155. package/dist/features/grouped/repository/grouped-listings.repository.d.ts +4 -0
  156. package/dist/features/grouped/repository/grouped-listings.repository.js +18 -0
  157. package/dist/features/homepage/components/CharacterHotspotForm.js +202 -187
  158. package/dist/features/homepage/components/SectionCarousel.d.ts +1 -7
  159. package/dist/features/homepage/components/SectionCarousel.js +1 -1
  160. package/dist/features/layout/TitleBarLayout.d.ts +1 -1
  161. package/dist/features/layout/TitleBarLayout.js +3 -3
  162. package/dist/features/media/MediaImage.js +39 -3
  163. package/dist/features/media/types/index.d.ts +10 -0
  164. package/dist/features/media/types/index.js +1 -0
  165. package/dist/features/media/upload/MediaUploadField.d.ts +7 -1
  166. package/dist/features/media/upload/MediaUploadField.js +11 -7
  167. package/dist/features/media/upload/video-poster.d.ts +2 -0
  168. package/dist/features/media/upload/video-poster.js +16 -1
  169. package/dist/features/media/upload/video-poster.test.js +1 -1
  170. package/dist/features/orders/components/OrderStatusTimeline.d.ts +12 -0
  171. package/dist/features/orders/components/OrderStatusTimeline.js +55 -0
  172. package/dist/features/orders/components/index.d.ts +2 -0
  173. package/dist/features/orders/components/index.js +1 -0
  174. package/dist/features/orders/constants/payment-window.d.ts +25 -0
  175. package/dist/features/orders/constants/payment-window.js +25 -0
  176. package/dist/features/orders/repository/orders.repository.d.ts +32 -0
  177. package/dist/features/orders/repository/orders.repository.js +56 -0
  178. package/dist/features/orders/schemas/index.d.ts +24 -24
  179. package/dist/features/orders/types/index.d.ts +33 -0
  180. package/dist/features/pre-orders/schemas/index.d.ts +5 -0
  181. package/dist/features/products/components/BidHistory.js +9 -1
  182. package/dist/features/products/components/GroupSettingsPanel.js +25 -3
  183. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  184. package/dist/features/products/components/PrizeDrawEntryActions.d.ts +1 -4
  185. package/dist/features/products/components/PrizeDrawEntryActions.js +1 -1
  186. package/dist/features/products/components/ProductDetailActions.d.ts +1 -2
  187. package/dist/features/products/components/ProductDetailActions.js +1 -1
  188. package/dist/features/products/components/ProductGrid.js +15 -1
  189. package/dist/features/products/constants/action-defs.d.ts +1 -0
  190. package/dist/features/products/constants/action-defs.js +1 -0
  191. package/dist/features/products/schemas/index.d.ts +5 -0
  192. package/dist/features/promotions/components/CouponsIndexListing.d.ts +1 -3
  193. package/dist/features/promotions/components/CouponsIndexListing.js +1 -1
  194. package/dist/features/search/components/SearchView.d.ts +1 -3
  195. package/dist/features/search/components/SearchView.js +1 -1
  196. package/dist/features/seller/components/QuickProductForm.js +2 -1
  197. package/dist/features/seller/components/SellerBidsView.js +66 -107
  198. package/dist/features/seller/components/SellerGoogleReviewsView.js +17 -1
  199. package/dist/features/seller/components/SellerOrdersView.d.ts +15 -3
  200. package/dist/features/seller/components/SellerOrdersView.js +134 -163
  201. package/dist/features/seller/components/SellerPayoutMethodsView.js +60 -48
  202. package/dist/features/seller/components/SellerPayoutSettingsView.js +69 -4
  203. package/dist/features/seller/components/SellerPayoutsView.d.ts +12 -0
  204. package/dist/features/seller/components/SellerPayoutsView.js +25 -5
  205. package/dist/features/seller/components/SellerProductShell.d.ts +10 -3
  206. package/dist/features/seller/components/SellerProductShell.js +89 -36
  207. package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +2 -7
  208. package/dist/features/seller/components/SellerProductsFilterDrawer.js +4 -4
  209. package/dist/features/seller/components/SellerProductsView.d.ts +2 -4
  210. package/dist/features/seller/components/SellerProductsView.js +131 -189
  211. package/dist/features/seller/components/SellerReviewsView.js +95 -133
  212. package/dist/features/seller/components/SellerShippingConfigsView.js +47 -67
  213. package/dist/features/seller/components/SellerShippingView.js +41 -4
  214. package/dist/features/seller/components/SellerStoreCategoriesView.js +51 -59
  215. package/dist/features/seller/components/SellerStorefrontView.d.ts +6 -1
  216. package/dist/features/seller/components/SellerStorefrontView.js +42 -5
  217. package/dist/features/seller/components/index.d.ts +2 -2
  218. package/dist/features/seller/components/index.js +2 -2
  219. package/dist/features/shell/FormShell.d.ts +24 -4
  220. package/dist/features/shell/FormShell.js +12 -6
  221. package/dist/features/shell/QuickFormDrawer.js +67 -12
  222. package/dist/features/shell/StepForm.d.ts +30 -1
  223. package/dist/features/shell/StepForm.js +47 -4
  224. package/dist/features/shell/index.d.ts +1 -1
  225. package/dist/features/shell/index.js +1 -1
  226. package/dist/features/shipments/components/AdminShipmentEditorView.js +2 -2
  227. package/dist/features/shipments/schemas/validation.d.ts +14 -4
  228. package/dist/features/tester/components/AdminTesterFeedbackListView.js +23 -2
  229. package/dist/features/tester/components/TesterChecklistStepRow.js +1 -1
  230. package/dist/features/tester/components/TesterHubView.js +3 -2
  231. package/dist/features/tester/seed-data/orders-tester-seed-data.js +9 -1
  232. package/dist/features/tester/seed-data/products-tester-seed-data.js +43 -32
  233. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +404 -15
  234. package/dist/index.d.ts +15 -3
  235. package/dist/index.js +22 -5
  236. package/dist/next/routing/route-map.d.ts +11 -0
  237. package/dist/next/routing/route-map.js +5 -0
  238. package/dist/providers/email-resend/provider.js +1 -0
  239. package/dist/providers/firebase-client/auth.d.ts +1 -0
  240. package/dist/providers/firebase-client/auth.js +7 -1
  241. package/dist/repositories/index.d.ts +1 -0
  242. package/dist/repositories/index.js +1 -0
  243. package/dist/schemas/registry.d.ts +61 -25
  244. package/dist/security/pii-mask.js +1 -1
  245. package/dist/seed/categories-seed-data.js +69 -0
  246. package/dist/seed/products-auctions-seed-data.js +11 -0
  247. package/dist/seed/products-standard-seed-data.js +93 -4
  248. package/dist/seed/site-settings-seed-data.js +13 -1
  249. package/dist/seed/store-extensions-seed-data.js +1 -1
  250. package/dist/server-entry.d.ts +2 -1
  251. package/dist/server-entry.js +20 -2
  252. package/dist/server.d.ts +7 -2
  253. package/dist/server.js +8 -3
  254. package/dist/styles.css +1 -1
  255. package/dist/tailwind-utilities.css +1 -1
  256. package/dist/ui/components/Form.d.ts +11 -2
  257. package/dist/ui/components/Form.js +4 -3
  258. package/dist/ui/components/FormField.d.ts +3 -1
  259. package/dist/ui/components/FormField.js +22 -4
  260. package/dist/ui/components/ListingLayout.d.ts +1 -2
  261. package/dist/ui/components/ListingLayout.js +1 -1
  262. package/dist/ui/components/RecordDetailModal.d.ts +68 -0
  263. package/dist/ui/components/RecordDetailModal.js +8 -0
  264. package/dist/ui/components/SlottedListingView.js +9 -1
  265. package/dist/ui/forms/FormErrorSummary.d.ts +25 -0
  266. package/dist/ui/forms/FormErrorSummary.js +37 -0
  267. package/dist/ui/forms/FormShell.d.ts +37 -2
  268. package/dist/ui/forms/FormShell.js +37 -7
  269. package/dist/ui/forms/FormShell.style.css +17 -0
  270. package/dist/ui/forms/index.d.ts +2 -0
  271. package/dist/ui/forms/index.js +1 -0
  272. package/dist/ui/index.d.ts +4 -0
  273. package/dist/ui/index.js +5 -0
  274. package/dist/utils/media-url.d.ts +3 -1
  275. package/dist/utils/media-url.js +20 -1
  276. package/dist/validation/schemas.d.ts +4 -4
  277. package/package.json +1 -1
  278. package/dist/_internal/client/features/classified/ClassifiedDetailView.d.ts +0 -9
  279. package/dist/_internal/client/features/classified/ClassifiedDetailView.js +0 -55
  280. package/dist/_internal/client/features/digital-code/DigitalCodeDetailView.d.ts +0 -18
  281. package/dist/_internal/client/features/digital-code/DigitalCodeDetailView.js +0 -21
  282. package/dist/_internal/client/features/live/LiveItemDetailView.d.ts +0 -9
  283. package/dist/_internal/client/features/live/LiveItemDetailView.js +0 -25
  284. package/dist/_internal/server/jobs/core/prizeRevealClose.d.ts +0 -2
  285. package/dist/_internal/server/jobs/core/prizeRevealClose.js +0 -28
  286. package/dist/_internal/server/jobs/core/prizeRevealExpiry.d.ts +0 -2
  287. package/dist/_internal/server/jobs/core/prizeRevealExpiry.js +0 -61
  288. package/dist/_internal/server/jobs/core/prizeRevealOpen.d.ts +0 -2
  289. package/dist/_internal/server/jobs/core/prizeRevealOpen.js +0 -68
  290. package/dist/_internal/server/jobs/core/prizeRevealReminder.d.ts +0 -2
  291. package/dist/_internal/server/jobs/core/prizeRevealReminder.js +0 -49
  292. package/dist/_internal/server/jobs/handlers/prizeRevealClose.d.ts +0 -2
  293. package/dist/_internal/server/jobs/handlers/prizeRevealClose.js +0 -2
  294. package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.d.ts +0 -2
  295. package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.js +0 -2
  296. package/dist/_internal/server/jobs/handlers/prizeRevealOpen.d.ts +0 -2
  297. package/dist/_internal/server/jobs/handlers/prizeRevealOpen.js +0 -2
  298. package/dist/_internal/server/jobs/handlers/prizeRevealReminder.d.ts +0 -2
  299. package/dist/_internal/server/jobs/handlers/prizeRevealReminder.js +0 -2
  300. package/dist/features/auth/consent-otp.d.ts +0 -57
  301. package/dist/features/auth/consent-otp.js +0 -127
  302. package/dist/features/cart/components/CheckoutOtpModal.d.ts +0 -15
  303. package/dist/features/cart/components/CheckoutOtpModal.js +0 -12
  304. package/dist/features/checkout/actions/checkout-actions.d.ts +0 -31
  305. package/dist/features/checkout/actions/checkout-actions.js +0 -124
  306. package/dist/features/classified/components/ClassifiedIndexListing.d.ts +0 -5
  307. package/dist/features/classified/components/ClassifiedIndexListing.js +0 -123
  308. package/dist/features/classified/components/ClassifiedListView.d.ts +0 -6
  309. package/dist/features/classified/components/ClassifiedListView.js +0 -44
  310. package/dist/features/digital-codes/components/DigitalCodesIndexListing.d.ts +0 -5
  311. package/dist/features/digital-codes/components/DigitalCodesIndexListing.js +0 -114
  312. package/dist/features/digital-codes/components/DigitalCodesListView.d.ts +0 -6
  313. package/dist/features/digital-codes/components/DigitalCodesListView.js +0 -38
  314. package/dist/features/live/components/LiveItemsIndexListing.d.ts +0 -5
  315. package/dist/features/live/components/LiveItemsIndexListing.js +0 -115
  316. package/dist/features/live/components/LiveItemsListView.d.ts +0 -6
  317. package/dist/features/live/components/LiveItemsListView.js +0 -44
  318. package/dist/features/seller/components/SellerTemplatesView.d.ts +0 -12
  319. package/dist/features/seller/components/SellerTemplatesView.js +0 -276
  320. package/dist/react/hooks/useVisibleItems.d.ts +0 -37
  321. package/dist/react/hooks/useVisibleItems.js +0 -59
  322. package/dist/ui/components/TabStrip.d.ts +0 -34
  323. package/dist/ui/components/TabStrip.js +0 -56
  324. package/dist/ui/components/TabStrip.style.css +0 -97
@@ -1,14 +1,26 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { normalizeError } from "../../../errors/normalize";
4
4
  import { useApiMutation } from "@mohasinac/appkit/client";
5
- import React from "react";
5
+ import React, { useEffect, useMemo } from "react";
6
+ import { z } from "zod";
6
7
  import { useQuery } from "@tanstack/react-query";
7
8
  import { Button, ConfirmDeleteModal, Div, Heading, Input, RichTextEditor, RichTextRenderer, Row, Select, Span, Stack, StackedViewShell, TagInput, Text, Toggle, useToast } from "../../../ui";
9
+ import { FormShellContext, useFormShellState, applyZodIssues, FormErrorSummary } from "../../../ui/forms";
8
10
  import { ImageUpload, MediaImage, useMediaUpload } from "../../media";
9
11
  import { apiClient } from "../../../http";
10
12
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
13
+ import { blogPostCategorySchema } from "../../blog/schemas";
11
14
  import { StepForm } from "../../shell";
15
+ // Form-input subset — `blogPostSchema` (blog schemas) requires `id` and uses
16
+ // a `MediaField` shape for `coverImage`, neither of which this draft has
17
+ // (coverImage here is a plain uploaded-URL string; id doesn't exist pre-save).
18
+ const blogDraftSchema = z.object({
19
+ title: z.string().min(1, "Title is required"),
20
+ slug: z.string().min(1, "Slug is required").regex(/^blog-/, "Slug must start with 'blog-'"),
21
+ category: blogPostCategorySchema,
22
+ metaDescription: z.string().max(160).optional().or(z.literal("")),
23
+ }).passthrough();
12
24
  const __P = {
13
25
  p4: "p-[var(--appkit-space-4)]",
14
26
  };
@@ -72,6 +84,11 @@ export function AdminBlogEditorView({ postId, onSaved, onDeleted, embedded, ...r
72
84
  const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
73
85
  const { showToast } = useToast();
74
86
  const { upload } = useMediaUpload();
87
+ const { shellCtx, setFieldError, validate } = useFormShellState(blogDraftSchema);
88
+ useEffect(() => {
89
+ validate(draft);
90
+ // eslint-disable-next-line react-hooks/exhaustive-deps
91
+ }, [draft, validate]);
75
92
  const update = React.useCallback((partial) => {
76
93
  setDraft((prev) => ({ ...prev, ...partial }));
77
94
  }, []);
@@ -146,6 +163,10 @@ export function AdminBlogEditorView({ postId, onSaved, onDeleted, embedded, ...r
146
163
  onSaved(id);
147
164
  },
148
165
  onError: (err) => {
166
+ const issues = err?.issues;
167
+ if (issues && issues.length > 0) {
168
+ applyZodIssues(issues, setFieldError);
169
+ }
149
170
  showToast(err?.message ?? "Failed to save post.", "error");
150
171
  },
151
172
  });
@@ -163,6 +184,7 @@ export function AdminBlogEditorView({ postId, onSaved, onDeleted, embedded, ...r
163
184
  const steps = [
164
185
  {
165
186
  label: "Content",
187
+ fields: ["title", "slug", "excerpt", "content"],
166
188
  validate: (values) => !values.title.trim() ? "Title is required" : null,
167
189
  render: ({ values, onChange }) => (_jsxs(Stack, { gap: "5", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Content" }), _jsx(Input, { label: "Title", value: values.title, onChange: (e) => {
168
190
  const v = e.target.value;
@@ -174,6 +196,7 @@ export function AdminBlogEditorView({ postId, onSaved, onDeleted, embedded, ...r
174
196
  },
175
197
  {
176
198
  label: "Media",
199
+ fields: ["coverImage", "youtubeId"],
177
200
  render: ({ values, onChange }) => (_jsxs(Stack, { gap: "5", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Media" }), _jsx(ImageUpload, { label: "Cover Image", currentImage: values.coverImage, onUpload: (file) => upload(file, "blog", true, {
178
201
  type: "blog-cover",
179
202
  title: values.title || values.slug,
@@ -182,6 +205,7 @@ export function AdminBlogEditorView({ postId, onSaved, onDeleted, embedded, ...r
182
205
  },
183
206
  {
184
207
  label: "SEO & Tags",
208
+ fields: ["category", "tags", "metaTitle", "metaDescription"],
185
209
  render: ({ values, onChange }) => {
186
210
  const readTime = Math.max(1, Math.round(values.content.replace(/<[^>]*>/g, "").split(/\s+/).filter(Boolean).length / 200));
187
211
  return (_jsxs(Stack, { gap: "5", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "SEO & Tags" }), _jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-2", children: [_jsx(Select, { label: "Category", options: CATEGORY_OPTIONS, value: values.category, onValueChange: (v) => onChange({ category: v }) }), _jsxs(Div, { children: [_jsx(Text, { className: "mb-1.5", size: "sm", weight: "medium", children: "Estimated Read Time" }), _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: ["~", readTime, " min (auto-calculated from content)"] })] })] }), _jsx(TagInput, { label: "Tags", value: values.tags, onChange: (t) => onChange({ tags: t }), placeholder: "e.g. pokemon, grading, tcg" }), _jsx(Input, { label: "Meta Title (optional)", value: values.metaTitle, onChange: (e) => onChange({ metaTitle: e.target.value }), placeholder: "Defaults to post title" }), _jsx(Input, { label: "Meta Description (optional)", value: values.metaDescription, onChange: (e) => onChange({ metaDescription: e.target.value }), placeholder: "SEO description \u2014 max 160 chars", maxLength: 160 })] }));
@@ -189,6 +213,7 @@ export function AdminBlogEditorView({ postId, onSaved, onDeleted, embedded, ...r
189
213
  },
190
214
  {
191
215
  label: "Publish",
216
+ fields: ["status", "publishedAt", "authorName", "isFeatured"],
192
217
  render: ({ values, onChange }) => (_jsxs(Stack, { gap: "5", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Publish Settings" }), _jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-2", children: [_jsx(Select, { label: "Status", options: STATUS_OPTIONS, value: values.status, onValueChange: (v) => onChange({ status: v }) }), _jsx(Input, { label: "Publish date (optional)", value: values.publishedAt, onChange: (e) => onChange({ publishedAt: e.target.value }), type: "date", helperText: "Auto-set to now when publishing." })] }), _jsx(Input, { label: "Author Name", value: values.authorName, onChange: (e) => onChange({ authorName: e.target.value }), placeholder: "Author display name" }), _jsx(Toggle, { label: "Featured post", checked: values.isFeatured, onChange: (checked) => onChange({ isFeatured: checked }) }), isEdit && (_jsx(Div, { className: "border-t border-[var(--appkit-color-border)]", padding: "t-md", children: _jsx(Button, { type: "button", variant: "danger", size: "sm", onClick: () => setShowDeleteConfirm(true), isLoading: deleteMutation.isPending, children: "Delete post" }) }))] })),
193
218
  },
194
219
  ];
@@ -196,7 +221,16 @@ export function AdminBlogEditorView({ postId, onSaved, onDeleted, embedded, ...r
196
221
  setShowDeleteConfirm(false);
197
222
  deleteMutation.mutate();
198
223
  }, onClose: () => setShowDeleteConfirm(false), title: "Delete post", message: "This will permanently delete the blog post. This cannot be undone." }));
199
- const formContent = (_jsxs(_Fragment, { children: [_jsx(StepForm, { steps: steps, values: draft, onChange: update, onComplete: () => { saveMutation.mutate(); }, formId: "admin-blog", currentStep: currentStep, onStepChange: setCurrentStep, completeLabel: isEdit ? "Save Changes" : "Create Post", isLoading: isLoading }), deleteModal] }));
224
+ const fieldToStepIndex = useMemo(() => {
225
+ const map = {};
226
+ steps.forEach((step, i) => {
227
+ step.fields?.forEach((field) => { map[field] = i; });
228
+ });
229
+ return map;
230
+ // eslint-disable-next-line react-hooks/exhaustive-deps
231
+ }, [steps.length]);
232
+ const wizardShellCtx = useMemo(() => ({ ...shellCtx, fieldToStepIndex, goToStep: (n) => setCurrentStep(n) }), [shellCtx, fieldToStepIndex, setCurrentStep]);
233
+ const formContent = (_jsxs(FormShellContext.Provider, { value: wizardShellCtx, children: [_jsx(FormErrorSummary, {}), _jsx(StepForm, { steps: steps, values: draft, onChange: update, onComplete: () => { saveMutation.mutate(); }, formId: "admin-blog", currentStep: currentStep, onStepChange: setCurrentStep, completeLabel: isEdit ? "Save Changes" : "Create Post", isLoading: isLoading }), deleteModal] }));
200
234
  if (embedded) {
201
235
  return _jsx(Div, { className: `${__O.yAuto} ${__P.p4}`, children: formContent });
202
236
  }
@@ -6,7 +6,7 @@ import React from "react";
6
6
  import { useQuery } from "@tanstack/react-query";
7
7
  import { z } from "zod";
8
8
  import { Button, ConfirmDeleteModal, Div, Form, Input, StackedViewShell, Toggle, useToast, } from "../../../ui";
9
- import { FieldInput } from "../../../ui/forms";
9
+ import { FieldInput, FormErrorSummary } from "../../../ui/forms";
10
10
  import { ImageUpload } from "../../media/upload/ImageUpload";
11
11
  import { useMediaUpload } from "../../media";
12
12
  import { apiClient } from "../../../http";
@@ -16,8 +16,9 @@ const brandFormSchema = z.object({
16
16
  slug: z.string().regex(/^[a-z0-9-]+$/i).optional().or(z.literal("")),
17
17
  description: z.string().max(2000).optional().or(z.literal("")),
18
18
  logoURL: z.string().url().optional().or(z.literal("")),
19
- bannerURL: z.string().url().optional().or(z.literal("")),
20
19
  website: z.string().url().optional().or(z.literal("")),
20
+ country: z.string().max(80).optional().or(z.literal("")),
21
+ founded: z.number().int().min(1800).max(new Date().getFullYear()).optional(),
21
22
  isActive: z.boolean(),
22
23
  displayOrder: z.number().int().min(0).optional(),
23
24
  });
@@ -38,8 +39,9 @@ export function AdminBrandEditorView({ brandId, onSaved, onDeleted, embedded, ..
38
39
  const [slugManual, setSlugManual] = React.useState(false);
39
40
  const [description, setDescription] = React.useState("");
40
41
  const [logoURL, setLogoURL] = React.useState("");
41
- const [bannerURL, setBannerURL] = React.useState("");
42
42
  const [website, setWebsite] = React.useState("");
43
+ const [country, setCountry] = React.useState("");
44
+ const [founded, setFounded] = React.useState("");
43
45
  const [isActive, setIsActive] = React.useState(true);
44
46
  const [displayOrder, setDisplayOrder] = React.useState("");
45
47
  const [deleteConfirmOpen, setDeleteConfirmOpen] = React.useState(false);
@@ -61,8 +63,9 @@ export function AdminBrandEditorView({ brandId, onSaved, onDeleted, embedded, ..
61
63
  setSlugManual(true);
62
64
  setDescription(brand.description ?? "");
63
65
  setLogoURL(brand.logoURL ?? "");
64
- setBannerURL(brand.bannerURL ?? "");
65
66
  setWebsite(brand.website ?? "");
67
+ setCountry(brand.country ?? "");
68
+ setFounded(brand.founded !== undefined ? String(brand.founded) : "");
66
69
  setIsActive(brand.isActive ?? true);
67
70
  setDisplayOrder(brand.displayOrder !== undefined ? String(brand.displayOrder) : "");
68
71
  }, [brandQuery.data]);
@@ -78,8 +81,9 @@ export function AdminBrandEditorView({ brandId, onSaved, onDeleted, embedded, ..
78
81
  slug: slug || toBrandSlug(name),
79
82
  description: description || undefined,
80
83
  logoURL: logoURL || undefined,
81
- bannerURL: bannerURL || undefined,
82
84
  website: website || undefined,
85
+ country: country || undefined,
86
+ founded: founded !== "" ? Number(founded) : undefined,
83
87
  isActive,
84
88
  displayOrder: displayOrder !== "" ? Number(displayOrder) : undefined,
85
89
  };
@@ -112,7 +116,7 @@ export function AdminBrandEditorView({ brandId, onSaved, onDeleted, embedded, ..
112
116
  const formSection = (_jsxs(_Fragment, { children: [_jsx(Form, { schema: brandFormSchema, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsxs(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-2", children: [_jsx(FieldInput, { name: "name", label: "Brand name", value: name, onChange: (v) => handleNameChange(v), required: true, placeholder: "e.g. Hot Wheels" }), _jsx(Input, { label: "Slug", value: slug, onChange: (e) => {
113
117
  setSlug(e.target.value);
114
118
  setSlugManual(true);
115
- }, placeholder: "brand-hot-wheels", helperText: "Auto-generated from name. Must start with 'brand-'." })] }), _jsx(Input, { label: "Description", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Brief description of the brand" }), _jsxs(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-2", children: [_jsx(ImageUpload, { label: "Logo", currentImage: logoURL, onUpload: (file) => upload(file, "brands", true, { type: "brand-logo", brand: name || slug }), onChange: setLogoURL }), _jsx(ImageUpload, { label: "Banner", currentImage: bannerURL, onUpload: (file) => upload(file, "brands", true, { type: "brand-banner", brand: name || slug }), onChange: setBannerURL })] }), _jsxs(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-2", children: [_jsx(Input, { label: "Website", value: website, onChange: (e) => setWebsite(e.target.value), placeholder: "https://brand.com", type: "url" }), _jsx(Input, { label: "Display order", value: displayOrder, onChange: (e) => setDisplayOrder(e.target.value), type: "number", min: 0, placeholder: "0" })] }), _jsx(Toggle, { label: "Active", checked: isActive, onChange: setIsActive }), _jsxs(Row, { gap: "3", padding: "t-xs", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !name || isSubmitting, onClick: () => {
119
+ }, placeholder: "brand-hot-wheels", helperText: "Auto-generated from name. Must start with 'brand-'." })] }), _jsx(Input, { label: "Description", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Brief description of the brand" }), _jsx(ImageUpload, { label: "Cover Image", currentImage: logoURL, onUpload: (file) => upload(file, "brands", true, { type: "brand-logo", brand: name || slug }), onChange: setLogoURL }), _jsxs(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-2", children: [_jsx(Input, { label: "Website", value: website, onChange: (e) => setWebsite(e.target.value), placeholder: "https://brand.com", type: "url" }), _jsx(Input, { label: "Display order", value: displayOrder, onChange: (e) => setDisplayOrder(e.target.value), type: "number", min: 0, placeholder: "0" })] }), _jsxs(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-2", children: [_jsx(Input, { label: "Country of origin", value: country, onChange: (e) => setCountry(e.target.value), placeholder: "e.g. Japan" }), _jsx(Input, { label: "Founded year", value: founded, onChange: (e) => setFounded(e.target.value), type: "number", min: 1800, max: new Date().getFullYear(), placeholder: "e.g. 1999" })] }), _jsx(Toggle, { label: "Active", checked: isActive, onChange: setIsActive }), _jsx(FormErrorSummary, {}), _jsxs(Row, { gap: "3", padding: "t-xs", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !name || isSubmitting, onClick: () => {
116
120
  clearErrors();
117
121
  if (!name.trim()) {
118
122
  setFieldError("name", "Brand name is required");
@@ -14,9 +14,10 @@ import { normalizeError } from "../../../errors/normalize";
14
14
  * static).
15
15
  */
16
16
  import React, { useCallback, useEffect, useState } from "react";
17
+ import { useQuery } from "@tanstack/react-query";
17
18
  import { z } from "zod";
18
19
  import { Button, Checkbox, ConfirmDeleteModal, Container, Form, Heading, Input, Row, Section, Select, Stack, Text, Textarea, useToast, } from "../../../ui";
19
- import { FieldInput } from "../../../ui/forms";
20
+ import { FieldInput, FormErrorSummary } from "../../../ui/forms";
20
21
  import { apiClient } from "../../../http";
21
22
  import { ADMIN_ENDPOINTS, SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
22
23
  import { BundleDynamicRuleEditor } from "../../categories/components/BundleDynamicRuleEditor";
@@ -42,7 +43,9 @@ const EMPTY_FORM = {
42
43
  dynamicRule: DEFAULT_DYNAMIC_RULE,
43
44
  isActive: true,
44
45
  coverImage: "",
46
+ brandSlug: "",
45
47
  };
48
+ const NO_BRAND_OPTION_VALUE = "";
46
49
  const bundleFormSchema = z.object({
47
50
  name: z.string().min(1, "Bundle name is required").max(150),
48
51
  description: z.string().max(2000).optional().or(z.literal("")),
@@ -70,6 +73,7 @@ function bundleToForm(bundle) {
70
73
  dynamicRule: isDynamic ? rule : DEFAULT_DYNAMIC_RULE,
71
74
  isActive: bundle.isActive !== false,
72
75
  coverImage: bundle.display?.coverImage ?? "",
76
+ brandSlug: bundle.brandSlug ?? "",
73
77
  };
74
78
  }
75
79
  function parsePriceRupees(input) {
@@ -96,6 +100,17 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, scope = "a
96
100
  const setFieldError = (path, message) => formHelpersRef.current?.setFieldError(path, message);
97
101
  const clearErrors = () => formHelpersRef.current?.clearErrors();
98
102
  const { showToast } = useToast();
103
+ const brandsQuery = useQuery({
104
+ queryKey: ["admin", "brands", "picker"],
105
+ queryFn: async () => {
106
+ const res = (await apiClient.get(ADMIN_ENDPOINTS.BRANDS));
107
+ return res?.data?.items ?? [];
108
+ },
109
+ });
110
+ const brandOptions = [
111
+ { label: "No specific brand", value: NO_BRAND_OPTION_VALUE },
112
+ ...(brandsQuery.data ?? []).map((b) => ({ label: b.name, value: b.slug ?? b.id })),
113
+ ];
99
114
  // Load existing bundle on mount when editing
100
115
  useEffect(() => {
101
116
  if (!bundleId)
@@ -166,6 +181,7 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, scope = "a
166
181
  ? { coverImage: form.coverImage.trim() }
167
182
  : undefined,
168
183
  isActive: form.isActive,
184
+ brandSlug: form.brandSlug || undefined,
169
185
  };
170
186
  if (isEdit && bundleId) {
171
187
  await apiClient.put(endpoints.byId(encodeURIComponent(bundleId)), body);
@@ -218,6 +234,6 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, scope = "a
218
234
  formHelpersRef.current = helpers;
219
235
  return (_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "lg", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "between", wrap: true, children: [_jsx(Heading, { level: 1, size: "2xl", weight: "semibold", color: "primary", children: isEdit
220
236
  ? BUNDLE_COPY.adminEditorTitleEdit
221
- : BUNDLE_COPY.adminEditorTitleNew }), isEdit && (_jsx(Button, { variant: "danger", onClick: () => setDeleteConfirmOpen(true), disabled: deleting, children: BUNDLE_COPY.adminEditor.deleteButton(deleting) }))] }), _jsx(ConfirmDeleteModal, { isOpen: deleteConfirmOpen, onClose: () => setDeleteConfirmOpen(false), onConfirm: handleDelete, title: "Delete this bundle?", message: BUNDLE_COPY.adminEditor.deleteConfirm, isDeleting: deleting }), apiError && (_jsx(Text, { color: "danger", role: "alert", children: apiError })), _jsxs(Stack, { gap: "md", children: [_jsx(FieldInput, { name: "name", label: BUNDLE_COPY.adminEditor.fields.nameLabel, value: form.name, onChange: (v) => setForm((f) => ({ ...f, name: v })), placeholder: BUNDLE_COPY.adminEditor.fields.namePlaceholder, disabled: saving, required: true }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.fields.descriptionLabel }), _jsx(Textarea, { value: form.description, onChange: (e) => setForm((f) => ({ ...f, description: e.target.value })), placeholder: BUNDLE_COPY.adminEditor.fields.descriptionPlaceholder, rows: 4, disabled: saving })] }), _jsx(Stack, { gap: "xs", children: _jsx(FieldInput, { name: "price", label: BUNDLE_COPY.adminEditor.fields.priceLabel, type: "number", inputMode: "decimal", min: 1, step: 1, value: form.priceRupees, onChange: (v) => setForm((f) => ({ ...f, priceRupees: v })), placeholder: BUNDLE_COPY.adminEditor.fields.pricePlaceholder, disabled: saving, required: true }) }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.fields.coverImageLabel }), _jsx(Input, { type: "url", value: form.coverImage, onChange: (e) => setForm((f) => ({ ...f, coverImage: e.target.value })), placeholder: "https://\u2026", disabled: saving })] }), _jsx(Checkbox, { checked: form.isActive, onChange: (e) => setForm((f) => ({ ...f, isActive: e.target.checked })), disabled: saving, label: BUNDLE_COPY.adminEditor.fields.activeLabel }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.ruleTypeLabel }), _jsx(Select, { options: RULE_TYPE_OPTIONS, value: form.ruleType, onValueChange: (next) => setForm((f) => ({ ...f, ruleType: next })), disabled: saving, "aria-label": BUNDLE_COPY.adminEditor.ruleTypeLabel })] }), form.ruleType === "static" ? (_jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { size: "sm", weight: "semibold", children: [BUNDLE_COPY.adminEditor.ruleTypeStatic, " products (", BUNDLE_MIN_ITEMS, "\u2013", BUNDLE_MAX_ITEMS, ")"] }), _jsx(ProductInlineSelect, { scope: scope, multiple: true, value: form.productIds, onChange: (ids) => setForm((f) => ({ ...f, productIds: ids })), disabled: saving, placeholder: "Search and select products\u2026" }), form.productIds.length > 0 && form.productIds.length < BUNDLE_MIN_ITEMS && (_jsxs(Text, { size: "xs", color: "danger", children: ["Select at least ", BUNDLE_MIN_ITEMS, " products (currently ", form.productIds.length, ")."] })), form.productIds.length > BUNDLE_MAX_ITEMS && (_jsxs(Text, { size: "xs", color: "danger", children: ["Maximum ", BUNDLE_MAX_ITEMS, " products allowed (currently ", form.productIds.length, ")."] }))] })) : (_jsx(BundleDynamicRuleEditor, { value: form.dynamicRule, onChange: (next) => setForm((f) => ({ ...f, dynamicRule: next })), disabled: saving }))] }), _jsx(Row, { gap: "sm", align: "center", justify: "end", children: _jsx(Button, { variant: "primary", onClick: handleSave, disabled: saving, "aria-busy": saving, children: BUNDLE_COPY.adminEditor.saveButton(saving, isEdit) }) })] }) }) }));
237
+ : BUNDLE_COPY.adminEditorTitleNew }), isEdit && (_jsx(Button, { variant: "danger", onClick: () => setDeleteConfirmOpen(true), disabled: deleting, children: BUNDLE_COPY.adminEditor.deleteButton(deleting) }))] }), _jsx(ConfirmDeleteModal, { isOpen: deleteConfirmOpen, onClose: () => setDeleteConfirmOpen(false), onConfirm: handleDelete, title: "Delete this bundle?", message: BUNDLE_COPY.adminEditor.deleteConfirm, isDeleting: deleting }), apiError && (_jsx(Text, { color: "danger", role: "alert", children: apiError })), _jsxs(Stack, { gap: "md", children: [_jsx(FieldInput, { name: "name", label: BUNDLE_COPY.adminEditor.fields.nameLabel, value: form.name, onChange: (v) => setForm((f) => ({ ...f, name: v })), placeholder: BUNDLE_COPY.adminEditor.fields.namePlaceholder, disabled: saving, required: true }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.fields.descriptionLabel }), _jsx(Textarea, { value: form.description, onChange: (e) => setForm((f) => ({ ...f, description: e.target.value })), placeholder: BUNDLE_COPY.adminEditor.fields.descriptionPlaceholder, rows: 4, disabled: saving })] }), _jsx(Stack, { gap: "xs", children: _jsx(FieldInput, { name: "price", label: BUNDLE_COPY.adminEditor.fields.priceLabel, type: "number", inputMode: "decimal", min: 1, step: 1, value: form.priceRupees, onChange: (v) => setForm((f) => ({ ...f, priceRupees: v })), placeholder: BUNDLE_COPY.adminEditor.fields.pricePlaceholder, disabled: saving, required: true }) }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.fields.coverImageLabel }), _jsx(Input, { type: "url", value: form.coverImage, onChange: (e) => setForm((f) => ({ ...f, coverImage: e.target.value })), placeholder: "https://\u2026", disabled: saving })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Brand" }), _jsx(Select, { options: brandOptions, value: form.brandSlug, onValueChange: (next) => setForm((f) => ({ ...f, brandSlug: next })), disabled: saving || brandsQuery.isLoading, "aria-label": "Brand" })] }), _jsx(Checkbox, { checked: form.isActive, onChange: (e) => setForm((f) => ({ ...f, isActive: e.target.checked })), disabled: saving, label: BUNDLE_COPY.adminEditor.fields.activeLabel }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.ruleTypeLabel }), _jsx(Select, { options: RULE_TYPE_OPTIONS, value: form.ruleType, onValueChange: (next) => setForm((f) => ({ ...f, ruleType: next })), disabled: saving, "aria-label": BUNDLE_COPY.adminEditor.ruleTypeLabel })] }), form.ruleType === "static" ? (_jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { size: "sm", weight: "semibold", children: [BUNDLE_COPY.adminEditor.ruleTypeStatic, " products (", BUNDLE_MIN_ITEMS, "\u2013", BUNDLE_MAX_ITEMS, ")"] }), _jsx(ProductInlineSelect, { scope: scope, multiple: true, value: form.productIds, onChange: (ids) => setForm((f) => ({ ...f, productIds: ids })), disabled: saving, placeholder: "Search and select products\u2026" }), form.productIds.length > 0 && form.productIds.length < BUNDLE_MIN_ITEMS && (_jsxs(Text, { size: "xs", color: "danger", children: ["Select at least ", BUNDLE_MIN_ITEMS, " products (currently ", form.productIds.length, ")."] })), form.productIds.length > BUNDLE_MAX_ITEMS && (_jsxs(Text, { size: "xs", color: "danger", children: ["Maximum ", BUNDLE_MAX_ITEMS, " products allowed (currently ", form.productIds.length, ")."] }))] })) : (_jsx(BundleDynamicRuleEditor, { value: form.dynamicRule, onChange: (next) => setForm((f) => ({ ...f, dynamicRule: next })), disabled: saving }))] }), _jsx(FormErrorSummary, {}), _jsx(Row, { gap: "sm", align: "center", justify: "end", children: _jsx(Button, { variant: "primary", onClick: handleSave, disabled: saving, "aria-busy": saving, children: BUNDLE_COPY.adminEditor.saveButton(saving, isEdit) }) })] }) }) }));
222
238
  } }));
223
239
  }
@@ -5,7 +5,7 @@ import React from "react";
5
5
  import { useQuery } from "@tanstack/react-query";
6
6
  import { z } from "zod";
7
7
  import { Alert, Button, ConfirmDeleteModal, Div, Form, FormActions, Heading, Input, Row, Select, Slider, Stack, StackedViewShell, Text, Toggle } from "../../../ui";
8
- import { FieldInput } from "../../../ui/forms";
8
+ import { FieldInput, FormErrorSummary } from "../../../ui/forms";
9
9
  import { apiClient } from "../../../http";
10
10
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
11
11
  import { ROUTES } from "../../../next/routing/route-map";
@@ -210,7 +210,7 @@ export function AdminCarouselEditorView({ slideId, onSaved, onDeleted, onCancel,
210
210
  return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: isEdit ? "Edit Carousel Slide" : "New Carousel Slide", sections: [
211
211
  errorMsg ? _jsx(Alert, { variant: "error", children: errorMsg }, "err") : null,
212
212
  successMsg ? _jsx(Alert, { variant: "success", children: successMsg }, "ok") : null,
213
- _jsx(Form, { schema: carouselSlideSchema, onSubmit: (e) => e.preventDefault(), spacing: "lg", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsxs(Div, { className: CLS_PANEL, children: [_jsx(Heading, { level: 3, className: CLS_SECTION_HEADING, children: "Slide info" }), _jsx(FieldInput, { name: "title", label: "Title", value: title, onChange: (v) => setTitle(v), required: true, placeholder: "e.g. Hot Wheels RLC Exclusives" }), _jsx(Toggle, { label: "Active (visible on homepage)", checked: active, onChange: setActive }), _jsx(Input, { label: "Display order", type: "number", value: order, onChange: (e) => setOrder(e.target.value), min: 0, placeholder: "1" }), _jsx(Select, { label: "Slide height", value: height, onChange: (e) => setHeight(e.target.value), options: HEIGHT_OPTIONS }), _jsx(Input, { label: "Autoplay delay (ms)", type: "number", value: autoplayDelayMs, onChange: (e) => setAutoplayDelayMs(e.target.value), min: 1000, max: 15000, placeholder: "4000", helperText: "How long this slide shows before auto-advancing. Default: 4000 ms." })] }), _jsxs(Div, { className: CLS_PANEL, children: [_jsx(Heading, { level: 3, className: CLS_SECTION_HEADING, children: "Background" }), _jsx(BackgroundEditor, { value: background, onChange: setBackground })] }), _jsxs(Div, { className: CLS_PANEL, children: [_jsx(Heading, { level: 3, className: CLS_SECTION_HEADING, children: "Overlay text (optional)" }), _jsx(Text, { size: "sm", color: "muted", children: "Centred text layered over the background. Leave blank to use cards only." }), _jsx(Input, { label: "Heading", value: overlayTitle, onChange: (e) => setOverlayTitle(e.target.value), placeholder: "India's #1 Collectibles Marketplace" }), _jsx(Input, { label: "Subtitle", value: overlaySubtitle, onChange: (e) => setOverlaySubtitle(e.target.value), placeholder: "Pok\u00E9mon TCG \u00B7 Hot Wheels \u00B7 Beyblade X" }), _jsx(Input, { label: "Description", value: overlayDesc, onChange: (e) => setOverlayDesc(e.target.value), placeholder: "One sentence description..." }), _jsxs(Stack, { className: `${__P.p3}`, gap: "sm", rounded: "lg", border: "default", children: [_jsx(Text, { size: "sm", weight: "medium", children: "CTA button" }), _jsx(Input, { label: "Button text", value: overlayBtnText, onChange: (e) => setOverlayBtnText(e.target.value), placeholder: "Shop Now" }), _jsx(Input, { label: "Button link", value: overlayBtnLink, onChange: (e) => setOverlayBtnLink(e.target.value), placeholder: String(ROUTES.PUBLIC.PRODUCTS) }), _jsx(Select, { label: "Variant", value: overlayBtnVariant, onChange: (e) => setOverlayBtnVariant(e.target.value), options: VARIANT_OPTIONS.filter((v) => ["primary", "secondary", "outline"].includes(v.value)) }), _jsx(Toggle, { label: "Open in new tab", checked: overlayBtnNewTab, onChange: setOverlayBtnNewTab })] })] }), _jsxs(Div, { className: CLS_PANEL, children: [_jsxs(Row, { className: CLS_ROW_BETWEEN, children: [_jsx(Heading, { level: 3, className: CLS_SECTION_HEADING, children: "Cards (0\u20132)" }), cards.length < 2 && (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: handleAddCard, children: "+ Add card" }))] }), cards.length === 0 && (_jsx(Text, { size: "sm", color: "faint", children: "No cards \u2014 the overlay text covers the whole slide." })), cards.map((card, i) => (_jsx(CardEditor, { card: card, index: i, otherZones: occupiedZones.filter((z) => z !== card.zone), onChange: (updated) => setCards(cards.map((c, ci) => (ci === i ? updated : c))), onRemove: () => setCards(cards.filter((_, ci) => ci !== i)) }, card.id)))] }), _jsxs(FormActions, { children: [_jsx(Button, { type: "submit", isLoading: saveMutation.isPending, disabled: !title || saveMutation.isPending, onClick: () => {
213
+ _jsx(Form, { schema: carouselSlideSchema, onSubmit: (e) => e.preventDefault(), spacing: "lg", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsxs(Div, { className: CLS_PANEL, children: [_jsx(Heading, { level: 3, className: CLS_SECTION_HEADING, children: "Slide info" }), _jsx(FieldInput, { name: "title", label: "Title", value: title, onChange: (v) => setTitle(v), required: true, placeholder: "e.g. Hot Wheels RLC Exclusives" }), _jsx(Toggle, { label: "Active (visible on homepage)", checked: active, onChange: setActive }), _jsx(Input, { label: "Display order", type: "number", value: order, onChange: (e) => setOrder(e.target.value), min: 0, placeholder: "1" }), _jsx(Select, { label: "Slide height", value: height, onChange: (e) => setHeight(e.target.value), options: HEIGHT_OPTIONS }), _jsx(Input, { label: "Autoplay delay (ms)", type: "number", value: autoplayDelayMs, onChange: (e) => setAutoplayDelayMs(e.target.value), min: 1000, max: 15000, placeholder: "4000", helperText: "How long this slide shows before auto-advancing. Default: 4000 ms." })] }), _jsxs(Div, { className: CLS_PANEL, children: [_jsx(Heading, { level: 3, className: CLS_SECTION_HEADING, children: "Background" }), _jsx(BackgroundEditor, { value: background, onChange: setBackground })] }), _jsxs(Div, { className: CLS_PANEL, children: [_jsx(Heading, { level: 3, className: CLS_SECTION_HEADING, children: "Overlay text (optional)" }), _jsx(Text, { size: "sm", color: "muted", children: "Centred text layered over the background. Leave blank to use cards only." }), _jsx(Input, { label: "Heading", value: overlayTitle, onChange: (e) => setOverlayTitle(e.target.value), placeholder: "India's #1 Collectibles Marketplace" }), _jsx(Input, { label: "Subtitle", value: overlaySubtitle, onChange: (e) => setOverlaySubtitle(e.target.value), placeholder: "Pok\u00E9mon TCG \u00B7 Hot Wheels \u00B7 Beyblade X" }), _jsx(Input, { label: "Description", value: overlayDesc, onChange: (e) => setOverlayDesc(e.target.value), placeholder: "One sentence description..." }), _jsxs(Stack, { className: `${__P.p3}`, gap: "sm", rounded: "lg", border: "default", children: [_jsx(Text, { size: "sm", weight: "medium", children: "CTA button" }), _jsx(Input, { label: "Button text", value: overlayBtnText, onChange: (e) => setOverlayBtnText(e.target.value), placeholder: "Shop Now" }), _jsx(Input, { label: "Button link", value: overlayBtnLink, onChange: (e) => setOverlayBtnLink(e.target.value), placeholder: String(ROUTES.PUBLIC.PRODUCTS) }), _jsx(Select, { label: "Variant", value: overlayBtnVariant, onChange: (e) => setOverlayBtnVariant(e.target.value), options: VARIANT_OPTIONS.filter((v) => ["primary", "secondary", "outline"].includes(v.value)) }), _jsx(Toggle, { label: "Open in new tab", checked: overlayBtnNewTab, onChange: setOverlayBtnNewTab })] })] }), _jsxs(Div, { className: CLS_PANEL, children: [_jsxs(Row, { className: CLS_ROW_BETWEEN, children: [_jsx(Heading, { level: 3, className: CLS_SECTION_HEADING, children: "Cards (0\u20132)" }), cards.length < 2 && (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: handleAddCard, children: "+ Add card" }))] }), cards.length === 0 && (_jsx(Text, { size: "sm", color: "faint", children: "No cards \u2014 the overlay text covers the whole slide." })), cards.map((card, i) => (_jsx(CardEditor, { card: card, index: i, otherZones: occupiedZones.filter((z) => z !== card.zone), onChange: (updated) => setCards(cards.map((c, ci) => (ci === i ? updated : c))), onRemove: () => setCards(cards.filter((_, ci) => ci !== i)) }, card.id)))] }), _jsx(FormErrorSummary, {}), _jsxs(FormActions, { children: [_jsx(Button, { type: "submit", isLoading: saveMutation.isPending, disabled: !title || saveMutation.isPending, onClick: () => {
214
214
  clearErrors();
215
215
  if (!title.trim()) {
216
216
  setFieldError("title", "Title is required");
@@ -4,7 +4,7 @@ import React from "react";
4
4
  import { z } from "zod";
5
5
  import { useRouter } from "next/navigation";
6
6
  import { Button, Form, Row, StackedViewShell, useToast } from "../../../ui";
7
- import { FieldInput, FieldSelect } from "../../../ui/forms";
7
+ import { FieldInput, FieldSelect, FormErrorSummary } from "../../../ui/forms";
8
8
  import { useApiMutation } from "@mohasinac/appkit/client";
9
9
  import { apiClient } from "../../../http";
10
10
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
@@ -38,7 +38,7 @@ export function AdminCarouselGroupEditorView({ onCreated, ...rest }) {
38
38
  _jsx(Form, { schema: carouselGroupFormSchema, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FieldInput, { name: "name", label: "Carousel name", value: name, onChange: (v) => setName(v), required: true, placeholder: "e.g. Homepage Hero" }), _jsx(FieldSelect, { name: "status", label: "Status", value: status, onChange: (v) => setStatus(v), options: [
39
39
  { value: "draft", label: "Draft" },
40
40
  { value: "active", label: "Active" },
41
- ] }), _jsx(Row, { gap: "3", padding: "t-xs", children: _jsx(Button, { type: "submit", isLoading: createMutation.isPending, disabled: !name || createMutation.isPending, onClick: () => {
41
+ ] }), _jsx(FormErrorSummary, {}), _jsx(Row, { gap: "3", padding: "t-xs", children: _jsx(Button, { type: "submit", isLoading: createMutation.isPending, disabled: !name || createMutation.isPending, onClick: () => {
42
42
  clearErrors();
43
43
  if (!name.trim()) {
44
44
  setFieldError("name", "Carousel name is required");
@@ -1,12 +1,34 @@
1
1
  "use client";
2
- import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { SIEVE_OP, sieveFilter } from "@mohasinac/appkit/client";
4
4
  import { sortBy } from "@mohasinac/appkit/client";
5
- import React from "react";
6
- import { FilterChipGroup, ListingLayout } from "../../../ui";
5
+ import React, { useState } from "react";
6
+ import { FilterChipGroup, ListingLayout, RecordDetailModal } from "../../../ui";
7
+ import { formatCurrency } from "../../../utils/number.formatter";
7
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
9
  import { toRecordArray, toRelativeDate, toStringValue, } from "../hooks/useAdminListingData";
9
10
  import { DataListingView } from "./DataListingView";
11
+ /**
12
+ * The cart's `items[]` is already in the list response — mapRows only ever
13
+ * read `.length` for the row subtitle and discarded the rest, leaving no way
14
+ * to see what's actually in a cart (Root Cause #52). This maps it for the
15
+ * detail modal.
16
+ */
17
+ function toCartItemEntries(raw) {
18
+ const items = Array.isArray(raw.items) ? raw.items : [];
19
+ return items
20
+ .filter((e) => Boolean(e) && typeof e === "object")
21
+ .map((e) => {
22
+ const qty = typeof e.quantity === "number" ? e.quantity : 1;
23
+ const price = typeof e.price === "number" ? e.price : undefined;
24
+ return {
25
+ image: typeof e.image === "string" ? e.image : undefined,
26
+ title: typeof e.title === "string" ? e.title : String(e.productId ?? "Item"),
27
+ subtitle: `Qty: ${qty}`,
28
+ trailing: price !== undefined ? formatCurrency(price * qty) : undefined,
29
+ };
30
+ });
31
+ }
10
32
  const ADMIN_CARTS_CONFIG = {
11
33
  portal: "admin",
12
34
  title: "Carts",
@@ -32,6 +54,7 @@ const ADMIN_CARTS_CONFIG = {
32
54
  secondary: `${itemCount} item${itemCount !== 1 ? "s" : ""}`,
33
55
  status: isGuest ? "Guest" : "Authenticated",
34
56
  updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
57
+ _raw: item,
35
58
  };
36
59
  }),
37
60
  getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
@@ -50,8 +73,15 @@ const ADMIN_CARTS_CONFIG = {
50
73
  ], value: pendingFilters.ownership ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, ownership: id })), allId: "" })),
51
74
  };
52
75
  export function AdminCartsView({ children, ...props }) {
76
+ const [selected, setSelected] = useState(null);
53
77
  if (React.Children.count(children) > 0) {
54
78
  return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
55
79
  }
56
- return _jsx(DataListingView, { config: ADMIN_CARTS_CONFIG });
80
+ const raw = selected?._raw ?? {};
81
+ const isGuest = !raw.userId;
82
+ return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: { ...ADMIN_CARTS_CONFIG, onRowClick: (row) => setSelected(row) } }), _jsx(RecordDetailModal, { isOpen: Boolean(selected), onClose: () => setSelected(null), title: "Cart Details", badges: [{ label: isGuest ? "Guest" : "Authenticated", variant: isGuest ? "default" : "info" }], fields: [
83
+ { label: isGuest ? "Session" : "User", value: String(raw.sessionId ?? raw.userId ?? "—") },
84
+ { label: "Last updated", value: selected?.updatedAt ?? "—" },
85
+ { label: "Cart ID", value: selected?.id ?? "—" },
86
+ ], items: { heading: "Items in cart", entries: toCartItemEntries(raw) } })] }));
57
87
  }
@@ -9,7 +9,7 @@ import { Button, Card, CardBody, ConfirmDeleteModal, Div, Form, Input, Paginated
9
9
  import { apiClient } from "../../../http";
10
10
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
11
11
  import { CategoryQuickCreateForm } from "./CategoryQuickCreateForm";
12
- import { FieldInput } from "../../../ui/forms";
12
+ import { FieldInput, FormErrorSummary, FormShellContext, useFormShellState } from "../../../ui/forms";
13
13
  const categoryFormSchema = z.object({
14
14
  name: z.string().min(1, "Category name is required").max(120),
15
15
  slug: z.string().regex(/^[a-z0-9-]+$/, "Lowercase letters, digits and hyphens only").optional().or(z.literal("")),
@@ -45,6 +45,7 @@ function toCategorySlug(str) {
45
45
  export function AdminCategoryEditorView({ categoryId, onSaved, onDeleted, embedded, ...rest }) {
46
46
  const isEdit = Boolean(categoryId);
47
47
  const [deleteOpen, setDeleteOpen] = React.useState(false);
48
+ const { shellCtx } = useFormShellState(categoryFormSchema);
48
49
  const [name, setName] = React.useState("");
49
50
  const [slug, setSlug] = React.useState("");
50
51
  const [slugManual, setSlugManual] = React.useState(false);
@@ -116,11 +117,11 @@ export function AdminCategoryEditorView({ categoryId, onSaved, onDeleted, embedd
116
117
  onError: (err) => showToast(err?.message ?? "Failed to delete category.", "error"),
117
118
  });
118
119
  const isSubmitting = saveMutation.isPending || categoryQuery.isLoading;
119
- const actionSidebar = (_jsxs(Card, { variant: "outlined", padding: "md", spacing: "sm", children: [_jsx(Text, { className: "tracking-widest", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "Status" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: isEdit ? (isActive ? "Active" : "Inactive") : "New" }), _jsx(Button, { type: "submit", form: "category-editor-form", className: "w-full", isLoading: isSubmitting, disabled: !name || isSubmitting, children: isEdit ? "Save changes" : "Create category" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", className: "w-full", isLoading: deleteMutation.isPending, onClick: () => setDeleteOpen(true), children: "Delete category" }))] }));
120
- const formContent = (_jsx(Form, { id: "category-editor-form", schema: categoryFormSchema, onSubmit: (e) => e.preventDefault(), spacing: "lg", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsxs(Card, { variant: "outlined", padding: "lg", children: [_jsx(Text, { className: "tracking-widest mb-4", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "Identity" }), _jsxs(Stack, { gap: "md", children: [_jsxs(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-2", children: [_jsx(FieldInput, { name: "name", label: "Category name", value: name, onChange: (v) => handleNameChange(v), required: true, placeholder: "e.g. Toys & Games" }), _jsx(Input, { label: "Slug", value: slug, onChange: (e) => {
120
+ const actionSidebar = (_jsx(FormShellContext.Provider, { value: shellCtx, children: _jsxs(Card, { variant: "outlined", padding: "md", spacing: "sm", children: [_jsx(Text, { className: "tracking-widest", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "Status" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: isEdit ? (isActive ? "Active" : "Inactive") : "New" }), _jsx(FormErrorSummary, {}), _jsx(Button, { type: "submit", form: "category-editor-form", className: "w-full", isLoading: isSubmitting, disabled: !name || isSubmitting, children: isEdit ? "Save changes" : "Create category" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", className: "w-full", isLoading: deleteMutation.isPending, onClick: () => setDeleteOpen(true), children: "Delete category" }))] }) }));
121
+ const formContent = (_jsx(Form, { id: "category-editor-form", schema: categoryFormSchema, shellCtx: shellCtx, onSubmit: (e) => e.preventDefault(), spacing: "lg", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsxs(Card, { variant: "outlined", padding: "lg", children: [_jsx(Text, { className: "tracking-widest mb-4", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "Identity" }), _jsxs(Stack, { gap: "md", children: [_jsxs(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-2", children: [_jsx(FieldInput, { name: "name", label: "Category name", value: name, onChange: (v) => handleNameChange(v), required: true, placeholder: "e.g. Toys & Games" }), _jsx(Input, { label: "Slug", value: slug, onChange: (e) => {
121
122
  setSlug(e.target.value);
122
123
  setSlugManual(true);
123
- }, placeholder: "toys-and-games", helperText: "Auto-generated from name. Used in URLs." })] }), _jsx(Input, { label: "Description", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Brief description of the category" }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Parent category" }), _jsx(PaginatedSelect, { value: parentId || null, onChange: (v) => setParentId(v ?? ""), loadOptions: loadCategoryOptions, placeholder: "Search categories\u2026 (leave empty for root)", searchPlaceholder: "Type category name\u2026", noResultsText: "No categories found", ariaLabel: "Parent category", createLabel: "Category", renderCreateForm: ({ onCreated, onCancel }) => (_jsx(CategoryQuickCreateForm, { onSaved: (id, n) => { setParentId(id); onCreated({ value: id, label: n }); }, onCancel: onCancel })) }), _jsx(Text, { size: "xs", color: "muted", children: "Leave empty to create a root category." })] })] })] }), _jsxs(Card, { variant: "outlined", padding: "lg", children: [_jsx(Text, { className: "tracking-widest mb-4", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "Display" }), _jsxs(Stack, { gap: "md", children: [_jsx(Input, { label: "Display order", value: order, onChange: (e) => setOrder(e.target.value), type: "number", min: 0, placeholder: "0" }), _jsx(Toggle, { label: "Active", checked: isActive, onChange: setIsActive }), _jsx(Toggle, { label: "Show in menu", checked: showInMenu, onChange: setShowInMenu })] })] }), _jsxs(Row, { gap: "3", className: "lg:hidden", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !name || isSubmitting, onClick: () => {
124
+ }, placeholder: "toys-and-games", helperText: "Auto-generated from name. Used in URLs." })] }), _jsx(Input, { label: "Description", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Brief description of the category" }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Parent category" }), _jsx(PaginatedSelect, { value: parentId || null, onChange: (v) => setParentId(v ?? ""), loadOptions: loadCategoryOptions, placeholder: "Search categories\u2026 (leave empty for root)", searchPlaceholder: "Type category name\u2026", noResultsText: "No categories found", ariaLabel: "Parent category", createLabel: "Category", renderCreateForm: ({ onCreated, onCancel }) => (_jsx(CategoryQuickCreateForm, { onSaved: (id, n) => { setParentId(id); onCreated({ value: id, label: n }); }, onCancel: onCancel })) }), _jsx(Text, { size: "xs", color: "muted", children: "Leave empty to create a root category." })] })] })] }), _jsxs(Card, { variant: "outlined", padding: "lg", children: [_jsx(Text, { className: "tracking-widest mb-4", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "Display" }), _jsxs(Stack, { gap: "md", children: [_jsx(Input, { label: "Display order", value: order, onChange: (e) => setOrder(e.target.value), type: "number", min: 0, placeholder: "0" }), _jsx(Toggle, { label: "Active", checked: isActive, onChange: setIsActive }), _jsx(Toggle, { label: "Show in menu", checked: showInMenu, onChange: setShowInMenu })] })] }), _jsx(FormErrorSummary, {}), _jsxs(Row, { gap: "3", className: "lg:hidden", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !name || isSubmitting, onClick: () => {
124
125
  clearErrors();
125
126
  if (!name.trim()) {
126
127
  setFieldError("name", "Category name is required");
@@ -7,7 +7,7 @@ import React from "react";
7
7
  import { useQuery } from "@tanstack/react-query";
8
8
  import { z } from "zod";
9
9
  import { Button, ConfirmDeleteModal, Div, Form, IconButton, Input, Label, Select, Span, Stack, StackedViewShell, Text, Toggle, useToast } from "../../../ui";
10
- import { FieldInput } from "../../../ui/forms";
10
+ import { FieldInput, FormErrorSummary } from "../../../ui/forms";
11
11
  import { apiClient } from "../../../http";
12
12
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
13
13
  import { ProductInlineSelect } from "../../seller/components/ProductInlineSelect";
@@ -212,7 +212,7 @@ export function AdminCouponEditorView({ couponId, onSaved, onDeleted, embedded,
212
212
  if (!id || applicableCategories.includes(id))
213
213
  return;
214
214
  setApplicableCategories([...applicableCategories, id]);
215
- }, allowCreate: true, placeholder: "Add a category\u2026" }), applicableCategories.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", padding: "t-2xs", children: applicableCategories.map((cid) => (_jsxs(Span, { layout: "inline-flex", gap: "xs", border: "strong", padding: "pill-sm", rounded: "full", surface: "muted", color: "primary", size: "xs", children: [cid, _jsx(IconButton, { "aria-label": `Remove ${cid}`, variant: "ghost", size: "sm", onClick: () => setApplicableCategories(applicableCategories.filter((c) => c !== cid)), icon: "\u00D7" })] }, cid))) })), _jsx(Text, { size: "xs", color: "muted", children: "Leave empty to apply the coupon to every category." })] }), _jsxs(Row, { gap: "3", padding: "t-xs", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !canSave || isSubmitting, onClick: () => {
215
+ }, allowCreate: true, placeholder: "Add a category\u2026" }), applicableCategories.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", padding: "t-2xs", children: applicableCategories.map((cid) => (_jsxs(Span, { layout: "inline-flex", gap: "xs", border: "strong", padding: "pill-sm", rounded: "full", surface: "muted", color: "primary", size: "xs", children: [cid, _jsx(IconButton, { "aria-label": `Remove ${cid}`, variant: "ghost", size: "sm", onClick: () => setApplicableCategories(applicableCategories.filter((c) => c !== cid)), icon: "\u00D7" })] }, cid))) })), _jsx(Text, { size: "xs", color: "muted", children: "Leave empty to apply the coupon to every category." })] }), _jsx(FormErrorSummary, {}), _jsxs(Row, { gap: "3", padding: "t-xs", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !canSave || isSubmitting, onClick: () => {
216
216
  clearErrors();
217
217
  if (!name.trim()) {
218
218
  setFieldError("name", "Campaign name is required");
@@ -6,7 +6,7 @@ import React from "react";
6
6
  import { useQuery } from "@tanstack/react-query";
7
7
  import { z } from "zod";
8
8
  import { Button, ConfirmDeleteModal, Div, Form, Input, RichTextEditor, Select, Stack, StackedViewShell, TagInput, Text, Toggle, useToast } from "../../../ui";
9
- import { FieldInput } from "../../../ui/forms";
9
+ import { FieldInput, FormErrorSummary } from "../../../ui/forms";
10
10
  import { apiClient } from "../../../http";
11
11
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
12
12
  const faqFormSchema = z.object({
@@ -135,7 +135,7 @@ export function AdminFaqEditorView({ faqId, onSaved, onDeleted, embedded, ...res
135
135
  const formSection = (_jsxs(_Fragment, { children: [_jsx(Form, { schema: faqFormSchema, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FieldInput, { name: "question", label: "Question", value: question, onChange: (v) => handleQuestionChange(v), required: true, placeholder: "e.g. How does bidding work on LetItRip?" }), _jsx(Input, { label: "Slug", value: slug, onChange: (e) => {
136
136
  setSlug(e.target.value);
137
137
  setSlugManual(true);
138
- }, placeholder: "faq-how-does-bidding-work", helperText: "Auto-generated from question. Must start with 'faq-'." }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Answer" }), _jsx(RichTextEditor, { value: answer, onChange: setAnswer, placeholder: "Write a clear, helpful answer...", minHeightClassName: "min-h-[200px]" })] }), _jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-2", children: [_jsx(Select, { label: "Category", options: CATEGORY_OPTIONS, value: category, onValueChange: setCategory }), _jsx(Input, { label: "Display order", value: String(order), onChange: (e) => setOrder(parseInt(e.target.value, 10) || 0), type: "number", min: 0, helperText: "Lower = shown first within category." })] }), _jsx(Input, { label: "Priority", value: String(priority), onChange: (e) => setPriority(parseInt(e.target.value, 10) || 0), type: "number", min: 0, helperText: "Higher priority FAQs appear first in search results." }), _jsx(TagInput, { label: "Tags", value: tags, onChange: setTags, placeholder: "e.g. shipping, pokemon, returns" }), _jsxs(Stack, { className: `${__P.p4}`, gap: "3", rounded: "lg", border: "default", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Visibility" }), _jsx(Toggle, { label: "Active (visible to users)", checked: isActive, onChange: setIsActive }), _jsx(Toggle, { label: "Pinned (always shown at top)", checked: isPinned, onChange: setIsPinned }), _jsx(Toggle, { label: "Show on homepage FAQ section", checked: showOnHomepage, onChange: setShowOnHomepage }), _jsx(Toggle, { label: "Show in footer FAQ links", checked: showInFooter, onChange: setShowInFooter })] }), _jsxs(Row, { gap: "3", padding: "t-xs", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !canSave || isSubmitting, onClick: () => {
138
+ }, placeholder: "faq-how-does-bidding-work", helperText: "Auto-generated from question. Must start with 'faq-'." }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Answer" }), _jsx(RichTextEditor, { value: answer, onChange: setAnswer, placeholder: "Write a clear, helpful answer...", minHeightClassName: "min-h-[200px]" })] }), _jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-2", children: [_jsx(Select, { label: "Category", options: CATEGORY_OPTIONS, value: category, onValueChange: setCategory }), _jsx(Input, { label: "Display order", value: String(order), onChange: (e) => setOrder(parseInt(e.target.value, 10) || 0), type: "number", min: 0, helperText: "Lower = shown first within category." })] }), _jsx(Input, { label: "Priority", value: String(priority), onChange: (e) => setPriority(parseInt(e.target.value, 10) || 0), type: "number", min: 0, helperText: "Higher priority FAQs appear first in search results." }), _jsx(TagInput, { label: "Tags", value: tags, onChange: setTags, placeholder: "e.g. shipping, pokemon, returns" }), _jsxs(Stack, { className: `${__P.p4}`, gap: "3", rounded: "lg", border: "default", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Visibility" }), _jsx(Toggle, { label: "Active (visible to users)", checked: isActive, onChange: setIsActive }), _jsx(Toggle, { label: "Pinned (always shown at top)", checked: isPinned, onChange: setIsPinned }), _jsx(Toggle, { label: "Show on homepage FAQ section", checked: showOnHomepage, onChange: setShowOnHomepage }), _jsx(Toggle, { label: "Show in footer FAQ links", checked: showInFooter, onChange: setShowInFooter })] }), _jsx(FormErrorSummary, {}), _jsxs(Row, { gap: "3", padding: "t-xs", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !canSave || isSubmitting, onClick: () => {
139
139
  clearErrors();
140
140
  if (!question.trim()) {
141
141
  setFieldError("question", "Question is required");
@@ -6,8 +6,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
6
6
  */
7
7
  import { sortBy } from "@mohasinac/appkit/client";
8
8
  import React from "react";
9
+ import { useRouter } from "next/navigation";
9
10
  import { ListingLayout } from "../../../ui";
10
11
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
12
+ import { ROUTES } from "../../../next/routing/route-map";
11
13
  import { toRecordArray, toRelativeDate, toStringValue, } from "../hooks/useAdminListingData";
12
14
  import { DataListingView } from "./DataListingView";
13
15
  const ADMIN_HISTORY_CONFIG = {
@@ -32,14 +34,22 @@ const ADMIN_HISTORY_CONFIG = {
32
34
  secondary: `${itemCount} of ${limit} items`,
33
35
  status: itemCount >= limit ? "At cap" : itemCount >= limit - 5 ? "Near cap" : "OK",
34
36
  updatedAt: toRelativeDate(item.updatedAt),
37
+ userId: toStringValue(item.userId, ""),
35
38
  };
36
39
  }),
37
40
  getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
38
41
  buildFilters: () => undefined,
39
42
  };
40
43
  export function AdminHistoryView({ children, ...props }) {
44
+ const router = useRouter();
41
45
  if (React.Children.count(children) > 0) {
42
46
  return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
43
47
  }
44
- return _jsx(DataListingView, { config: ADMIN_HISTORY_CONFIG });
48
+ // Same shape as AdminWishlistsView: the list API returns only a per-user
49
+ // summary (never the viewed items), so the row already shows its whole
50
+ // record and the useful destination is the owning user's admin page.
51
+ return (_jsx(DataListingView, { config: {
52
+ ...ADMIN_HISTORY_CONFIG,
53
+ onRowClick: (row) => row.userId && router.push(String(ROUTES.ADMIN.USER_DETAIL(row.userId))),
54
+ } }));
45
55
  }
@@ -14,6 +14,7 @@ import { toRecordArray, toRelativeDate, toStringValue, } from "../hooks/useAdmin
14
14
  import { DataListingView } from "./DataListingView";
15
15
  import { apiClient } from "../../../http";
16
16
  import { NOTIFICATION_TYPES } from "../../../constants/notification-types";
17
+ import { ViewNotificationModal } from "./ViewNotificationModal";
17
18
  // W1-33 — sourced from shared NOTIFICATION_TYPES registry, prefixed with "All".
18
19
  const NOTIF_TYPES = ["All", ...NOTIFICATION_TYPES];
19
20
  export function AdminNotificationsView({ children, ...props }) {
@@ -21,6 +22,7 @@ export function AdminNotificationsView({ children, ...props }) {
21
22
  const { showToast } = useToast();
22
23
  const [deleteTarget, setDeleteTarget] = useState(null);
23
24
  const [bulkDeleteIds, setBulkDeleteIds] = useState(null);
25
+ const [viewNotification, setViewNotification] = useState(null);
24
26
  const bulkNotifs = useBulkAction({
25
27
  mutationFn: (payload) => apiClient.post(ADMIN_ENDPOINTS.ADMIN_NOTIFICATIONS_BULK, payload),
26
28
  onSuccess: (result) => {
@@ -59,6 +61,24 @@ export function AdminNotificationsView({ children, ...props }) {
59
61
  if (React.Children.count(children) > 0) {
60
62
  return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
61
63
  }
64
+ const openNotification = (row) => {
65
+ const raw = row._raw;
66
+ setViewNotification({
67
+ id: row.id,
68
+ userId: toStringValue(raw.userId, ""),
69
+ type: toStringValue(raw.type, ""),
70
+ priority: toStringValue(raw.priority, "") || undefined,
71
+ title: toStringValue(raw.title, "Notification"),
72
+ message: toStringValue(raw.message, ""),
73
+ imageUrl: toStringValue(raw.imageUrl, "") || undefined,
74
+ actionUrl: toStringValue(raw.actionUrl, "") || undefined,
75
+ actionLabel: toStringValue(raw.actionLabel, "") || undefined,
76
+ isRead: Boolean(raw.isRead),
77
+ relatedId: toStringValue(raw.relatedId, "") || undefined,
78
+ relatedType: toStringValue(raw.relatedType, "") || undefined,
79
+ createdAt: toStringValue(raw.createdAt, "") || undefined,
80
+ });
81
+ };
62
82
  const config = {
63
83
  portal: "admin",
64
84
  title: "Notifications",
@@ -81,6 +101,7 @@ export function AdminNotificationsView({ children, ...props }) {
81
101
  status: Boolean(item.isRead) ? "Read" : "Unread",
82
102
  updatedAt: toRelativeDate(item.createdAt),
83
103
  type: toStringValue(item.type, ""),
104
+ _raw: item,
84
105
  })),
85
106
  getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
86
107
  buildFilters: (state) => {
@@ -93,6 +114,7 @@ export function AdminNotificationsView({ children, ...props }) {
93
114
  parts.push(sieveFilter("isRead", SIEVE_OP.EQ, true));
94
115
  return parts.join(",") || undefined;
95
116
  },
117
+ onRowClick: (row) => openNotification(row),
96
118
  // Rule #7: bulk-action array sourced from the ADMIN_BULK_ACTIONS preset.
97
119
  buildBulkActions: (selection) => ADMIN_BULK_ACTIONS.notifications.map((id) => ({
98
120
  id,
@@ -111,6 +133,10 @@ export function AdminNotificationsView({ children, ...props }) {
111
133
  },
112
134
  })),
113
135
  renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
136
+ {
137
+ label: "View details",
138
+ onClick: () => openNotification(row),
139
+ },
114
140
  {
115
141
  label: ACTIONS.ADMIN["resend-notification"].label,
116
142
  onClick: () => resendMutation.mutate(row.id),
@@ -139,5 +165,5 @@ export function AdminNotificationsView({ children, ...props }) {
139
165
  }
140
166
  }, isDeleting: deleteMutation.isPending || bulkNotifs.isLoading, title: bulkDeleteIds ? "Delete these notifications?" : "Delete notification?", message: bulkDeleteIds
141
167
  ? `${bulkDeleteIds.length} notification(s) will be permanently removed.`
142
- : "This notification will be permanently removed.", confirmText: "Delete", variant: "danger" })] }));
168
+ : "This notification will be permanently removed.", confirmText: "Delete", variant: "danger" }), _jsx(ViewNotificationModal, { notification: viewNotification, isOpen: Boolean(viewNotification), onClose: () => setViewNotification(null) })] }));
143
169
  }
@@ -1,10 +1,19 @@
1
1
  import React from "react";
2
+ export interface AdminOrderItemRow {
3
+ productId: string;
4
+ title: string;
5
+ image?: string;
6
+ quantity: number;
7
+ unitPrice: number;
8
+ totalPrice: number;
9
+ }
2
10
  export interface AdminOrderEditorViewProps {
3
11
  open: boolean;
4
12
  onClose: () => void;
5
13
  orderId?: string;
6
14
  orderLabel?: string;
7
15
  currentStatus?: string;
16
+ items?: AdminOrderItemRow[];
8
17
  paymentProofUrl?: string;
9
18
  paymentTransactionId?: string;
10
19
  paymentMethod?: string;
@@ -15,4 +24,4 @@ export interface AdminOrderEditorViewProps {
15
24
  buyerMarkedPaid?: boolean;
16
25
  buyerFraudAgreementAccepted?: boolean;
17
26
  }
18
- export declare function AdminOrderEditorView({ open, onClose, orderId, orderLabel, currentStatus, paymentProofUrl, paymentTransactionId, paymentMethod, paymentStatus, displayedUpiId, buyerReportedUpiId, paymentUpiMismatch, buyerMarkedPaid, buyerFraudAgreementAccepted, }: AdminOrderEditorViewProps): React.JSX.Element;
27
+ export declare function AdminOrderEditorView({ open, onClose, orderId, orderLabel, currentStatus, items, paymentProofUrl, paymentTransactionId, paymentMethod, paymentStatus, displayedUpiId, buyerReportedUpiId, paymentUpiMismatch, buyerMarkedPaid, buyerFraudAgreementAccepted, }: AdminOrderEditorViewProps): React.JSX.Element;