@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
@@ -10,10 +10,58 @@ import { QuickEditMenu } from "./QuickEditMenu";
10
10
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
11
11
  import { ROUTES } from "../../../next/routing/route-map";
12
12
  import { ADMIN_BULK_ACTIONS, ROW_ACTION_META, ROW_ACTION_ID } from "../../products/constants/action-defs";
13
- import { ADMIN_ORDER_STATUS_TABS } from "../constants/filter-tabs";
13
+ import { ADMIN_ORDER_STATUS_TABS, ADMIN_ORDER_PAYMENT_REVIEW_TABS } from "../constants/filter-tabs";
14
+ import { isManualPaymentMethod, isPaymentReviewQueueMode } from "../../orders/constants/payment-window";
14
15
  import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../hooks/useAdminListingData";
15
16
  import { DataListingView } from "./DataListingView";
16
17
  import { AdminOrderEditorView } from "./AdminOrderEditorView";
18
+ /**
19
+ * `OrderDocument` has no `orderNumber` field — the real item info an order
20
+ * list row should show lives in the denormalized `items[]` snapshot
21
+ * (`productTitle`/`image`), written at order time specifically so list/detail
22
+ * UI never needs an extra product fetch. Falling back to `Order {id}` (a raw
23
+ * GUID) instead was Root Cause Pattern #(order-guid) — see CLAUDE.md.
24
+ */
25
+ function toOrderItemRows(item) {
26
+ const items = Array.isArray(item.items) ? item.items : [];
27
+ return items
28
+ .filter((entry) => Boolean(entry) && typeof entry === "object")
29
+ .map((entry) => ({
30
+ productId: typeof entry.productId === "string" ? entry.productId : "",
31
+ title: typeof entry.productTitle === "string" ? entry.productTitle : "Item",
32
+ image: typeof entry.image === "string" ? entry.image : undefined,
33
+ quantity: typeof entry.quantity === "number" ? entry.quantity : 1,
34
+ unitPrice: typeof entry.unitPrice === "number" ? entry.unitPrice : 0,
35
+ totalPrice: typeof entry.totalPrice === "number" ? entry.totalPrice : 0,
36
+ }));
37
+ }
38
+ function firstOrderItem(item) {
39
+ const items = toOrderItemRows(item);
40
+ return {
41
+ image: items[0]?.image,
42
+ title: items[0]?.title,
43
+ extraCount: Math.max(0, items.length - 1),
44
+ };
45
+ }
46
+ /**
47
+ * Short marker appended to a row's secondary line so a manual payment needing
48
+ * action is visible in the list itself, not only after opening the drawer.
49
+ * Mirrors the same `paymentProofUrl` / `paymentReviewOutcome` derivation the
50
+ * server-side queue uses (`listPaymentReviewQueue`) — kept in sync by reading
51
+ * the same two fields rather than a denormalised flag (Root Cause #42).
52
+ */
53
+ function paymentReviewLabel(item) {
54
+ if (!isManualPaymentMethod(toStringValue(item.paymentMethod, "")))
55
+ return undefined;
56
+ if (toStringValue(item.paymentStatus, "") === "paid")
57
+ return "Payment verified";
58
+ const outcome = toStringValue(item.paymentReviewOutcome, "");
59
+ if (outcome === "rejected_fraud")
60
+ return "Payment rejected";
61
+ if (outcome === "reupload_requested")
62
+ return "Re-upload requested";
63
+ return item.paymentProofUrl ? "Awaiting verification" : "Awaiting payment";
64
+ }
17
65
  export function AdminOrdersView({ children, ...props }) {
18
66
  const { showToast } = useToast();
19
67
  const router = useRouter();
@@ -37,7 +85,7 @@ export function AdminOrdersView({ children, ...props }) {
37
85
  title: "Orders",
38
86
  searchPlaceholder: "Search orders, buyers, or tracking IDs",
39
87
  emptyLabel: "No orders found",
40
- filterKeys: ["status"],
88
+ filterKeys: ["status", "paymentReview"],
41
89
  defaultSort: sortBy("createdAt", "DESC"),
42
90
  queryKey: ["admin", "orders", "listing"],
43
91
  endpoint: ADMIN_ENDPOINTS.ORDERS,
@@ -45,27 +93,47 @@ export function AdminOrdersView({ children, ...props }) {
45
93
  { value: sortBy("createdAt", "DESC"), label: "Newest" },
46
94
  { value: sortBy("createdAt", "ASC"), label: "Oldest" },
47
95
  ],
48
- mapRows: (response) => toRecordArray(response.orders).map((item, index) => ({
49
- id: toStringValue(item.id, `order-${index}`),
50
- primary: `Order ${toStringValue(item.orderNumber ?? item.id, "-")}`,
51
- secondary: [
52
- toStringValue(item.buyerName ?? item.customerName, "Unknown buyer"),
53
- toCurrency(item.totalAmount ?? item.total ?? item.amount),
54
- ].join(" · "),
55
- status: toStringValue(item.status, "Unknown"),
56
- updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
57
- paymentProofUrl: toStringValue(item.paymentProofUrl, "") || undefined,
58
- paymentTransactionId: toStringValue(item.paymentTransactionId, "") || undefined,
59
- paymentMethod: toStringValue(item.paymentMethod, "") || undefined,
60
- paymentStatus: toStringValue(item.paymentStatus, "") || undefined,
61
- displayedUpiId: toStringValue(item.displayedUpiId, "") || undefined,
62
- buyerReportedUpiId: toStringValue(item.buyerReportedUpiId, "") || undefined,
63
- paymentUpiMismatch: Boolean(item.paymentUpiMismatch),
64
- buyerMarkedPaid: Boolean(item.buyerMarkedPaid),
65
- buyerFraudAgreementAccepted: Boolean(item.buyerFraudAgreementAccepted),
66
- })),
96
+ mapRows: (response) => toRecordArray(response.orders).map((item, index) => {
97
+ const orderId = toStringValue(item.id, `order-${index}`);
98
+ const { image, title, extraCount } = firstOrderItem(item);
99
+ return {
100
+ id: orderId,
101
+ primary: title
102
+ ? `${title}${extraCount > 0 ? ` +${extraCount} more` : ""}`
103
+ : `Order ${orderId}`,
104
+ secondary: [
105
+ toStringValue(item.buyerName ?? item.customerName, "Unknown buyer"),
106
+ toCurrency(item.totalAmount ?? item.total ?? item.amount),
107
+ orderId.slice(0, 14),
108
+ paymentReviewLabel(item),
109
+ ]
110
+ .filter(Boolean)
111
+ .join(" · "),
112
+ status: toStringValue(item.status, "Unknown"),
113
+ updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
114
+ image,
115
+ items: toOrderItemRows(item),
116
+ paymentProofUrl: toStringValue(item.paymentProofUrl, "") || undefined,
117
+ paymentTransactionId: toStringValue(item.paymentTransactionId, "") || undefined,
118
+ paymentMethod: toStringValue(item.paymentMethod, "") || undefined,
119
+ paymentStatus: toStringValue(item.paymentStatus, "") || undefined,
120
+ displayedUpiId: toStringValue(item.displayedUpiId, "") || undefined,
121
+ buyerReportedUpiId: toStringValue(item.buyerReportedUpiId, "") || undefined,
122
+ paymentUpiMismatch: Boolean(item.paymentUpiMismatch),
123
+ buyerMarkedPaid: Boolean(item.buyerMarkedPaid),
124
+ buyerFraudAgreementAccepted: Boolean(item.buyerFraudAgreementAccepted),
125
+ };
126
+ }),
67
127
  getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
68
- buildFilters: (f) => (f.status && f.status !== "All" ? sieveFilter("status", SIEVE_OP.EQ, f.status) : undefined),
128
+ // The payment-review queue is always `status == pending` by definition, so
129
+ // a status chip on top of it could only ever narrow to zero rows. When the
130
+ // queue is active the status filter is dropped rather than combined.
131
+ buildFilters: (f) => {
132
+ if (isPaymentReviewQueueMode(f.paymentReview ?? ""))
133
+ return undefined;
134
+ return f.status && f.status !== "All" ? sieveFilter("status", SIEVE_OP.EQ, f.status) : undefined;
135
+ },
136
+ buildExtraParams: (f) => isPaymentReviewQueueMode(f.paymentReview ?? "") ? { paymentReview: f.paymentReview } : undefined,
69
137
  // Mirrors the row action's "View full details" entry so the row itself
70
138
  // is clickable, not just the overflow menu.
71
139
  onRowClick: (row) => {
@@ -128,7 +196,7 @@ export function AdminOrdersView({ children, ...props }) {
128
196
  submitLabel: "Update",
129
197
  },
130
198
  ] })),
131
- renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_ORDER_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) })),
199
+ renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_ORDER_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id, paymentReview: "" })) }), _jsx(FilterChipGroup, { label: "Manual payment", tabs: ADMIN_ORDER_PAYMENT_REVIEW_TABS, value: pendingFilters.paymentReview ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, paymentReview: id, status: "" })) })] })),
132
200
  };
133
- return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(AdminOrderEditorView, { open: drawerOpen, onClose: () => setDrawerOpen(false), orderId: selectedRow?.id, orderLabel: selectedRow?.primary, currentStatus: selectedRow?.status, paymentProofUrl: selectedRow?.paymentProofUrl, paymentTransactionId: selectedRow?.paymentTransactionId, paymentMethod: selectedRow?.paymentMethod, paymentStatus: selectedRow?.paymentStatus, displayedUpiId: selectedRow?.displayedUpiId, buyerReportedUpiId: selectedRow?.buyerReportedUpiId, paymentUpiMismatch: selectedRow?.paymentUpiMismatch, buyerMarkedPaid: selectedRow?.buyerMarkedPaid, buyerFraudAgreementAccepted: selectedRow?.buyerFraudAgreementAccepted })] }));
201
+ return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(AdminOrderEditorView, { open: drawerOpen, onClose: () => setDrawerOpen(false), orderId: selectedRow?.id, orderLabel: selectedRow?.primary, currentStatus: selectedRow?.status, items: selectedRow?.items, paymentProofUrl: selectedRow?.paymentProofUrl, paymentTransactionId: selectedRow?.paymentTransactionId, paymentMethod: selectedRow?.paymentMethod, paymentStatus: selectedRow?.paymentStatus, displayedUpiId: selectedRow?.displayedUpiId, buyerReportedUpiId: selectedRow?.buyerReportedUpiId, paymentUpiMismatch: selectedRow?.paymentUpiMismatch, buyerMarkedPaid: selectedRow?.buyerMarkedPaid, buyerFraudAgreementAccepted: selectedRow?.buyerFraudAgreementAccepted })] }));
134
202
  }
@@ -0,0 +1,7 @@
1
+ export interface AdminPayoutMarkPaidModalProps {
2
+ isOpen: boolean;
3
+ payoutId: string | null;
4
+ onClose: () => void;
5
+ onSuccess?: () => void;
6
+ }
7
+ export declare function AdminPayoutMarkPaidModal({ isOpen, payoutId, onClose, onSuccess }: AdminPayoutMarkPaidModalProps): import("react").JSX.Element;
@@ -0,0 +1,47 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * AdminPayoutMarkPaidModal — the single "mark payout as paid" form + mutation,
5
+ * shared by the payouts list row action and the standalone payout detail
6
+ * page so the two surfaces can't drift (same class of fix as
7
+ * SellerOrderDetailPanel for orders — see CLAUDE.md Root Cause list).
8
+ */
9
+ import { useState } from "react";
10
+ import { useApiMutation } from "@mohasinac/appkit/client";
11
+ import { useQueryClient } from "@tanstack/react-query";
12
+ import { Button, Form, FormActions, Input, Modal, useToast } from "../../../ui";
13
+ import { apiClient } from "../../../http";
14
+ import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
15
+ export function AdminPayoutMarkPaidModal({ isOpen, payoutId, onClose, onSuccess }) {
16
+ const [transactionId, setTransactionId] = useState("");
17
+ const { showToast } = useToast();
18
+ const queryClient = useQueryClient();
19
+ const markPaid = useApiMutation({
20
+ mutationFn: () => {
21
+ if (!payoutId)
22
+ throw new Error("No payout selected");
23
+ return apiClient.patch(ADMIN_ENDPOINTS.PAYOUT_BY_ID(payoutId), {
24
+ status: "paid",
25
+ transactionId: transactionId.trim() || undefined,
26
+ });
27
+ },
28
+ onSuccess: () => {
29
+ showToast("Payout marked as paid.", "success");
30
+ setTransactionId("");
31
+ void queryClient.invalidateQueries({ queryKey: ["admin", "payouts", "listing"] });
32
+ onSuccess?.();
33
+ onClose();
34
+ },
35
+ onError: () => {
36
+ showToast("Failed to update payout.", "error");
37
+ },
38
+ });
39
+ const handleClose = () => {
40
+ setTransactionId("");
41
+ onClose();
42
+ };
43
+ return (_jsx(Modal, { isOpen: isOpen, onClose: handleClose, title: "Mark payout as paid", children: _jsxs(Form, { onSubmit: (e) => {
44
+ e.preventDefault();
45
+ markPaid.mutate();
46
+ }, spacing: "md", children: [_jsx(Input, { label: "Transaction / reference ID", value: transactionId, onChange: (e) => setTransactionId(e.target.value), placeholder: "UTR, UPI ref, or bank transfer ID (optional)" }), _jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "outline", onClick: handleClose, children: "Cancel" }), _jsx(Button, { type: "submit", variant: "primary", disabled: markPaid.isPending, children: markPaid.isPending ? "Saving..." : "Confirm paid" })] })] }) }));
47
+ }
@@ -1,13 +1,16 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { normalizeError } from "../../../errors/normalize";
4
- import { useApiMutation, useBulkEvent, RTDB_PATHS } from "@mohasinac/appkit/client";
4
+ import { useBulkEvent, RTDB_PATHS } from "@mohasinac/appkit/client";
5
5
  import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit/client";
6
6
  import { sortBy } from "@mohasinac/appkit/client";
7
7
  import React, { useState, useEffect } from "react";
8
8
  import { useQueryClient } from "@tanstack/react-query";
9
- import { Button, FilterChipGroup, Form, FormActions, Input, ListingLayout, Modal, RowActionMenu, useToast, } from "../../../ui";
9
+ import { Button, FilterChipGroup, ListingLayout, RowActionMenu, useToast, } from "../../../ui";
10
+ import { AdminPayoutMarkPaidModal } from "./AdminPayoutMarkPaidModal";
11
+ import { useRouter } from "next/navigation";
10
12
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
13
+ import { ROUTES } from "../../../next/routing/route-map";
11
14
  import { ADMIN_PAYOUT_STATUS_TABS } from "../constants/filter-tabs";
12
15
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
13
16
  import { ADMIN_BULK_ACTIONS, ROW_ACTION_META } from "../../products/constants/action-defs";
@@ -15,9 +18,9 @@ import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../ho
15
18
  import { DataListingView } from "./DataListingView";
16
19
  import { apiClient } from "../../../http";
17
20
  export function AdminPayoutsView({ children, ...props }) {
21
+ const router = useRouter();
18
22
  const [markPaidOpen, setMarkPaidOpen] = useState(false);
19
23
  const [selectedPayoutId, setSelectedPayoutId] = useState(null);
20
- const [transactionId, setTransactionId] = useState("");
21
24
  const [calculating, setCalculating] = useState(false);
22
25
  const toast = useToast();
23
26
  const queryClient = useQueryClient();
@@ -57,26 +60,6 @@ export function AdminPayoutsView({ children, ...props }) {
57
60
  }
58
61
  // eslint-disable-next-line react-hooks/exhaustive-deps
59
62
  }, [bulkEvent.status]);
60
- const markPaid = useApiMutation({
61
- mutationFn: () => {
62
- if (!selectedPayoutId)
63
- throw new Error("No payout selected");
64
- return apiClient.patch(ADMIN_ENDPOINTS.PAYOUT_BY_ID(selectedPayoutId), {
65
- status: "paid",
66
- transactionId: transactionId.trim() || undefined,
67
- });
68
- },
69
- onSuccess: () => {
70
- toast.showToast("Payout marked as paid.", "success");
71
- setMarkPaidOpen(false);
72
- setSelectedPayoutId(null);
73
- setTransactionId("");
74
- void queryClient.invalidateQueries({ queryKey: ["admin", "payouts", "listing"] });
75
- },
76
- onError: () => {
77
- toast.showToast("Failed to update payout.", "error");
78
- },
79
- });
80
63
  const handleExportCsv = async () => {
81
64
  try {
82
65
  const blob = await apiClient.blob(ADMIN_ENDPOINTS.PAYOUTS_EXPORT);
@@ -92,10 +75,6 @@ export function AdminPayoutsView({ children, ...props }) {
92
75
  toast.showToast("CSV export failed.", "error");
93
76
  }
94
77
  };
95
- const closePaidModal = () => {
96
- setMarkPaidOpen(false);
97
- setTransactionId("");
98
- };
99
78
  if (React.Children.count(children) > 0) {
100
79
  return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
101
80
  }
@@ -121,6 +100,7 @@ export function AdminPayoutsView({ children, ...props }) {
121
100
  updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
122
101
  })),
123
102
  getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
103
+ onRowClick: (row) => router.push(String(ROUTES.ADMIN.PAYOUT_DETAIL(row.id))),
124
104
  buildFilters: (state) => {
125
105
  // An explicit status chip always wins. Otherwise, hide the noise
126
106
  // (already-paid payouts) by default — mirrors the isSold-hide-by-
@@ -143,6 +123,10 @@ export function AdminPayoutsView({ children, ...props }) {
143
123
  onClick: () => selection.clearSelection(),
144
124
  })),
145
125
  renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
126
+ {
127
+ label: "View full page",
128
+ onClick: () => router.push(String(ROUTES.ADMIN.PAYOUT_DETAIL(row.id))),
129
+ },
146
130
  {
147
131
  label: ACTIONS.ADMIN["grant-payout"].label,
148
132
  onClick: () => {
@@ -157,8 +141,8 @@ export function AdminPayoutsView({ children, ...props }) {
157
141
  { id: "true", label: "Show all" },
158
142
  ], value: pendingFilters.showAllPayouts ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, showAllPayouts: id })), allId: "" })] })),
159
143
  };
160
- return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(Modal, { isOpen: markPaidOpen, onClose: closePaidModal, title: "Mark payout as paid", children: _jsxs(Form, { onSubmit: (e) => {
161
- e.preventDefault();
162
- markPaid.mutate();
163
- }, spacing: "md", children: [_jsx(Input, { label: "Transaction / reference ID", value: transactionId, onChange: (e) => setTransactionId(e.target.value), placeholder: "UTR, UPI ref, or bank transfer ID (optional)" }), _jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "outline", onClick: closePaidModal, children: "Cancel" }), _jsx(Button, { type: "submit", variant: "primary", disabled: markPaid.isPending, children: markPaid.isPending ? "Saving..." : "Confirm paid" })] })] }) })] }));
144
+ return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(AdminPayoutMarkPaidModal, { isOpen: markPaidOpen, payoutId: selectedPayoutId, onClose: () => {
145
+ setMarkPaidOpen(false);
146
+ setSelectedPayoutId(null);
147
+ } })] }));
164
148
  }
@@ -6,8 +6,21 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
6
6
  import { Row } from "@mohasinac/appkit/ui";
7
7
  import { useApiMutation } from "@mohasinac/appkit/client";
8
8
  import React from "react";
9
+ import { z } from "zod";
9
10
  import { useQuery } from "@tanstack/react-query";
10
11
  import { Button, Card, CardBody, ConfirmDeleteModal, Div, PaginatedSelect, Form, Stack, StackedViewShell, Tabs, TabsContent, TabsList, TabsTrigger, Text, useToast, Show, StickyToolbar, } from "../../../ui";
12
+ import { useFormShellState, applyZodIssues, FormErrorSummary, FormShellContext } from "../../../ui/forms";
13
+ // Admin form-input validation — deliberately a small local schema, not the
14
+ // broader `productItemSchema` (that one requires `id`/`price`/`status` as
15
+ // non-optional, since it's the full read/display shape used across many
16
+ // features — applying it directly here would show false "required" errors
17
+ // on every keystroke while creating a new product, before an id exists).
18
+ const adminProductFormSchema = z.object({
19
+ title: z.string().min(1, "Product title is required"),
20
+ price: z.number().min(0).optional(),
21
+ images: z.array(z.string()).max(5, "Up to 5 gallery images allowed").optional(),
22
+ video: z.object({ url: z.string(), thumbnailUrl: z.string().optional() }).optional(),
23
+ }).passthrough();
11
24
  import { apiClient } from "../../../http";
12
25
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
13
26
  import { ProductForm } from "../../products/components/ProductForm";
@@ -106,6 +119,12 @@ export function AdminProductEditorView({ productId, onSaved, onDeleted, embedded
106
119
  const [mode, setMode] = React.useState("standard");
107
120
  const [deleteOpen, setDeleteOpen] = React.useState(false);
108
121
  const { showToast } = useToast();
122
+ const { shellCtx, setFieldError, validate } = useFormShellState(adminProductFormSchema);
123
+ // Live validation — re-run on every product change, not just on submit.
124
+ React.useEffect(() => {
125
+ validate(product);
126
+ // eslint-disable-next-line react-hooks/exhaustive-deps
127
+ }, [product, validate]);
109
128
  const productQuery = useQuery({
110
129
  queryKey: ["admin", "product", productId],
111
130
  queryFn: async () => {
@@ -153,6 +172,10 @@ export function AdminProductEditorView({ productId, onSaved, onDeleted, embedded
153
172
  onSaved(id);
154
173
  },
155
174
  onError: (err) => {
175
+ const issues = err?.issues;
176
+ if (issues && issues.length > 0) {
177
+ applyZodIssues(issues, setFieldError);
178
+ }
156
179
  showToast(err?.message ?? "Failed to save product.", "error");
157
180
  },
158
181
  });
@@ -168,13 +191,13 @@ export function AdminProductEditorView({ productId, onSaved, onDeleted, embedded
168
191
  },
169
192
  });
170
193
  const isSubmitting = saveMutation.isPending || productQuery.isLoading;
171
- 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 ? product.status ?? "—" : "New" }), _jsx(Button, { type: "submit", form: "product-editor-form", className: "w-full", isLoading: isSubmitting, disabled: !product.title || isSubmitting, children: isEdit ? "Save changes" : "Create product" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", className: "w-full", isLoading: deleteMutation.isPending, onClick: () => setDeleteOpen(true), children: "Delete product" }))] }));
172
- const formContent = (_jsxs(Form, { id: "product-editor-form", onSubmit: (e) => {
194
+ 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 ? product.status ?? "—" : "New" }), _jsx(FormErrorSummary, {}), _jsx(Button, { type: "submit", form: "product-editor-form", className: "w-full", isLoading: isSubmitting, disabled: !product.title || isSubmitting, children: isEdit ? "Save changes" : "Create product" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", className: "w-full", isLoading: deleteMutation.isPending, onClick: () => setDeleteOpen(true), children: "Delete product" }))] }) }));
195
+ const formContent = (_jsxs(Form, { id: "product-editor-form", shellCtx: shellCtx, onSubmit: (e) => {
173
196
  e.preventDefault();
174
197
  saveMutation.mutate();
175
198
  }, spacing: "lg", children: [_jsxs(Card, { variant: "outlined", padding: "lg", children: [_jsx(Text, { className: __P.sectionLabel, color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "Listing Type" }), _jsxs(Tabs, { value: mode, onChange: handleModeChange, children: [_jsxs(TabsList, { children: [(!enabledListingTypes || enabledListingTypes.includes("standard")) && (_jsx(TabsTrigger, { value: "standard", children: "Standard" })), (!enabledListingTypes || enabledListingTypes.includes("auction")) && (_jsx(TabsTrigger, { value: "auction", children: "Auction" })), (!enabledListingTypes || enabledListingTypes.includes("pre-order")) && (_jsx(TabsTrigger, { value: "preorder", children: "Pre-order" })), (!enabledListingTypes || enabledListingTypes.includes("prize-draw")) && (_jsx(TabsTrigger, { value: "prize-draw", children: "Prize Draw" })), (!enabledListingTypes || enabledListingTypes.includes("classified")) && (_jsx(TabsTrigger, { value: "classified", children: "Classified" })), (!enabledListingTypes || enabledListingTypes.includes("digital-code")) && (_jsx(TabsTrigger, { value: "digital-code", children: "Digital Code" })), (!enabledListingTypes || enabledListingTypes.includes("live")) && (_jsx(TabsTrigger, { value: "live", children: "Live Item" }))] }), _jsx(TabsContent, { value: "standard" }), _jsx(TabsContent, { value: "auction" }), _jsx(TabsContent, { value: "preorder" }), _jsx(TabsContent, { value: "prize-draw" }), _jsx(TabsContent, { value: "classified" }), _jsx(TabsContent, { value: "digital-code" }), _jsx(TabsContent, { value: "live" })] })] }), _jsxs(Card, { variant: "outlined", padding: "lg", children: [_jsx(Text, { className: __P.sectionLabel, color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "Classification" }), _jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", color: "primary", children: "Store" }), _jsx(PaginatedSelect, { value: product.storeId ?? null, onChange: handleStoreSelect, loadOptions: loadStoreOptions, placeholder: "Search stores\u2026", searchPlaceholder: "Type store name\u2026", noResultsText: "No stores found", ariaLabel: "Store" })] }), _jsx(ProductForm, { product: product, onChange: setProduct, renderGroupSettings: isEdit && productId
176
199
  ? (p) => (_jsx(GroupSettingsPanel, { productId: productId, productSlug: p.slug ?? productId, groupId: p.groupId, isGroupParent: p.isGroupParent, groupParentSlug: p.groupParentSlug, groupChildSlugs: p.groupChildSlugs, groupTitle: p.groupTitle, isAuction: modeFromProduct(p) === "auction", storeProductsEndpoint: ADMIN_ENDPOINTS.PRODUCTS, onGroupChanged: () => productQuery.refetch() }))
177
- : undefined, renderGroupJoinField: ({ label, value, onChange, disabled }) => (_jsx(GroupInlineSelect, { scope: "admin", value: value ?? "", onChange: (id) => onChange(id || undefined), disabled: disabled, label: label, placeholder: "None (standalone product)", allowCreate: false })), renderCategorySelector: ({ label, value, onChange, disabled }) => (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", color: "primary", children: label }), _jsx(PaginatedSelect, { value: value || null, onChange: (v) => onChange(v ?? ""), loadOptions: loadCategoryOptions, placeholder: "Search categories\u2026", searchPlaceholder: "Type category name\u2026", noResultsText: "No categories found", ariaLabel: label, disabled: disabled, createLabel: "Category", renderCreateForm: ({ onCreated, onCancel }) => (_jsx(CategoryQuickCreateForm, { onSaved: (id, name) => onCreated({ value: id, label: name }), onCancel: onCancel })) })] })), renderBrandSelector: (args) => (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", color: "primary", children: args.label }), _jsx(PaginatedSelect, { value: args.multi ? null : (args.value || null), onChange: (v) => args.onValueChange(v ?? ""), loadOptions: loadBrandOptions, placeholder: "Search brands\u2026", searchPlaceholder: "Type brand name\u2026", noResultsText: "No brands found", ariaLabel: args.label, disabled: args.disabled, createLabel: "Brand", renderCreateForm: ({ onCreated, onCancel }) => (_jsx(BrandQuickCreateForm, { onSaved: (id, name) => onCreated({ value: id, label: name }), onCancel: onCancel })) })] })) })] })] }), _jsxs(Card, { variant: "outlined", padding: "lg", children: [_jsx(Text, { className: __P.sectionLabel, color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "Inventory" }), _jsx(BarcodeField, { value: product.barcodeId ?? "", onChange: (v) => setProduct((prev) => ({ ...prev, barcodeId: v || undefined })), onScan: (v) => setProduct((prev) => ({ ...prev, barcodeId: v })), helperText: "Leave blank to auto-generate. Scan a pre-printed sticker to link it." })] }), _jsxs(Row, { gap: "3", className: "lg:hidden", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !product.title || isSubmitting, children: isEdit ? "Save changes" : "Create product" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => setDeleteOpen(true), children: "Delete product" }))] })] }, "product-form"));
200
+ : undefined, renderGroupJoinField: ({ label, value, onChange, disabled }) => (_jsx(GroupInlineSelect, { scope: "admin", value: value ?? "", onChange: (id) => onChange(id || undefined), disabled: disabled, label: label, placeholder: "None (standalone product)", allowCreate: false })), renderCategorySelector: ({ label, value, onChange, disabled }) => (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", color: "primary", children: label }), _jsx(PaginatedSelect, { value: value || null, onChange: (v) => onChange(v ?? ""), loadOptions: loadCategoryOptions, placeholder: "Search categories\u2026", searchPlaceholder: "Type category name\u2026", noResultsText: "No categories found", ariaLabel: label, disabled: disabled, createLabel: "Category", renderCreateForm: ({ onCreated, onCancel }) => (_jsx(CategoryQuickCreateForm, { onSaved: (id, name) => onCreated({ value: id, label: name }), onCancel: onCancel })) })] })), renderBrandSelector: (args) => (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", color: "primary", children: args.label }), _jsx(PaginatedSelect, { value: args.multi ? null : (args.value || null), onChange: (v) => args.onValueChange(v ?? ""), loadOptions: loadBrandOptions, placeholder: "Search brands\u2026", searchPlaceholder: "Type brand name\u2026", noResultsText: "No brands found", ariaLabel: args.label, disabled: args.disabled, createLabel: "Brand", renderCreateForm: ({ onCreated, onCancel }) => (_jsx(BrandQuickCreateForm, { onSaved: (id, name) => onCreated({ value: id, label: name }), onCancel: onCancel })) })] })) })] })] }), _jsxs(Card, { variant: "outlined", padding: "lg", children: [_jsx(Text, { className: __P.sectionLabel, color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "Inventory" }), _jsx(BarcodeField, { value: product.barcodeId ?? "", onChange: (v) => setProduct((prev) => ({ ...prev, barcodeId: v || undefined })), onScan: (v) => setProduct((prev) => ({ ...prev, barcodeId: v })), helperText: "Leave blank to auto-generate. Scan a pre-printed sticker to link it." })] }), _jsx(FormErrorSummary, {}), _jsxs(Row, { gap: "3", className: "lg:hidden", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !product.title || isSubmitting, children: isEdit ? "Save changes" : "Create product" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => setDeleteOpen(true), children: "Delete product" }))] })] }, "product-form"));
178
201
  if (embedded) {
179
202
  return _jsx(Div, { className: `${__O.yAuto} ${__P.p4}`, children: formContent });
180
203
  }
@@ -19,6 +19,13 @@ const __O = {
19
19
  hidden: "overflow-hidden",
20
20
  };
21
21
  // --- Helpers -----------------------------------------------------------------
22
+ /** Parses a newline- or comma-separated address list into a clean string[]. */
23
+ function splitEmailList(raw) {
24
+ return raw
25
+ .split(/[\n,]/)
26
+ .map((entry) => entry.trim())
27
+ .filter((entry) => entry.length > 0);
28
+ }
22
29
  function MaskedInput({ label, value, onChange, placeholder, helperText, }) {
23
30
  const [revealed, setRevealed] = React.useState(false);
24
31
  return (_jsxs(Div, { className: "relative", children: [_jsx(Input, { label: label, value: value, onChange: (e) => onChange(e.target.value), type: revealed ? "text" : "password", placeholder: placeholder, helperText: helperText }), _jsx(Button, { type: "button", variant: "ghost", textSize: "xs", textColor: "faint", paddingX: "none", paddingY: "none", onClick: () => setRevealed((r) => !r), className: "absolute right-3 top-8", children: revealed ? "Hide" : "Reveal" })] }));
@@ -250,6 +257,10 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
250
257
  const [resendApiKey, setResendApiKey] = React.useState("");
251
258
  const [notifFromEmail, setNotifFromEmail] = React.useState("");
252
259
  const [notifFromName, setNotifFromName] = React.useState("");
260
+ // Daily ops digest — recipients are edited as one-per-line text, split on save.
261
+ const [digestEnabled, setDigestEnabled] = React.useState(false);
262
+ const [digestRecipients, setDigestRecipients] = React.useState("");
263
+ const [digestCcRecipients, setDigestCcRecipients] = React.useState("");
253
264
  // Snapshot of the masked placeholder strings the server returned for every
254
265
  // credentials.* field, captured once per load. A combined single-save has
255
266
  // to send the FULL credentials object on every save (not just the group the
@@ -428,6 +439,9 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
428
439
  setResendApiKey(s.credentialsMasked?.resendApiKey ?? "");
429
440
  setNotifFromEmail(s.emailSettings?.fromEmail ?? "");
430
441
  setNotifFromName(s.emailSettings?.fromName ?? "");
442
+ setDigestEnabled(s.emailSettings?.dailyDigest?.enabled ?? false);
443
+ setDigestRecipients((s.emailSettings?.dailyDigest?.recipients ?? []).join("\n"));
444
+ setDigestCcRecipients((s.emailSettings?.dailyDigest?.ccRecipients ?? []).join("\n"));
431
445
  originalMaskedRef.current = {
432
446
  razorpayKeyId: s.credentialsMasked?.razorpayKeyId ?? "",
433
447
  razorpaySecret: s.credentialsMasked?.razorpaySecret ?? "",
@@ -558,6 +572,11 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
558
572
  emailSettings: {
559
573
  host: smtpHost, port: Number(smtpPort), user: smtpUser, fromAddress: smtpFrom,
560
574
  fromEmail: notifFromEmail, fromName: notifFromName,
575
+ dailyDigest: {
576
+ enabled: digestEnabled,
577
+ recipients: splitEmailList(digestRecipients),
578
+ ccRecipients: splitEmailList(digestCcRecipients),
579
+ },
561
580
  },
562
581
  integrations: { googleAnalyticsId: gaMeasurementId, facebookPixelId: fbPixelId, gtmContainerId },
563
582
  shipping: { freeShippingThreshold, defaultCarrier, maxDeliveryRadius },
@@ -656,7 +675,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
656
675
  ...bidIncrementTiers.slice(0, -1),
657
676
  { upTo: 100, increment: 10 },
658
677
  bidIncrementTiers[bidIncrementTiers.length - 1] ?? { upTo: null, increment: 1000 },
659
- ]), children: "+ Add tier" })] }), _jsx(Input, { label: "Auto-extend window (minutes before end)", value: String(autoExtendWindow), onChange: (e) => setAutoExtendWindow(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Extend auction end time if a bid arrives within this window." }), _jsx(Input, { label: "Settlement grace period (hours)", value: String(settlementGrace), onChange: (e) => setSettlementGrace(parseInt(e.target.value) || 0), type: "number", min: 1, helperText: "Time winner has to pay before the auction is re-listed." })] }) }), _jsx(TabsContent, { value: "limits", children: _jsx(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Max products per store", value: String(maxProductsPerStore), onChange: (e) => setMaxProductsPerStore(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max images per product", value: String(maxImagesPerProduct), onChange: (e) => setMaxImagesPerProduct(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max video size (MB)", value: String(maxVideoSizeMb), onChange: (e) => setMaxVideoSizeMb(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max custom fields per product", value: String(maxCustomFields), onChange: (e) => setMaxCustomFields(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Max custom sections per product", value: String(maxCustomSections), onChange: (e) => setMaxCustomSections(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Order cancellation window (hours)", value: String(orderCancelWindow), onChange: (e) => setOrderCancelWindow(parseInt(e.target.value) || 0), type: "number", min: 0 })] }) }) }), _jsx(TabsContent, { value: "whatsapp", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Platform-level WhatsApp Business Cloud API credentials. Used for automated purchase announcements to admin numbers when orders are placed. Store owners configure their own credentials in Store \u2192 WhatsApp." }), _jsx(Input, { label: "Phone Number ID", value: waPhoneNumberId, onChange: (e) => setWaPhoneNumberId(e.target.value), placeholder: "987654321098765", helperText: "From Meta for Developers \u2192 App \u2192 WhatsApp \u2192 API Setup" }), _jsx(MaskedInput, { label: "Cloud API System User Token", value: waCloudApiToken, onChange: setWaCloudApiToken, placeholder: "EAAxxxxxxxx\u2026", helperText: "Long-lived system user access token with WhatsApp Business permissions" }), _jsx(Input, { label: "Admin Notify Numbers", value: waAdminNotifyNumbers, onChange: (e) => setWaAdminNotifyNumbers(e.target.value), placeholder: "919876543210,918765432109", helperText: "Comma-separated, digits-only, include country code. These receive a WhatsApp message when any order is placed." }), _jsx(Text, { size: "xs", color: "muted", children: "Approved Meta message templates for the buyer-facing WhatsApp order-updates addon (Fees tab). Meta rejects business-initiated free-text WhatsApp messages outside a 24h customer-service window \u2014 a real template name here is required for that notification type to actually deliver. Leave blank to skip WhatsApp for that type (in-app + email still fire)." }), _jsx(Input, { label: "Template language code", value: waTemplateLanguage, onChange: (e) => setWaTemplateLanguage(e.target.value), placeholder: "en", helperText: "BCP-47 code the templates below were approved in, e.g. en or en_US." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Order placed template name", value: waTemplateOrderPlaced, onChange: (e) => setWaTemplateOrderPlaced(e.target.value), placeholder: "order_placed_update" }), _jsx(Input, { label: "Order confirmed template name", value: waTemplateOrderConfirmed, onChange: (e) => setWaTemplateOrderConfirmed(e.target.value), placeholder: "order_confirmed_update" }), _jsx(Input, { label: "Order shipped template name", value: waTemplateOrderShipped, onChange: (e) => setWaTemplateOrderShipped(e.target.value), placeholder: "order_shipped_update" }), _jsx(Input, { label: "Order delivered template name", value: waTemplateOrderDelivered, onChange: (e) => setWaTemplateOrderDelivered(e.target.value), placeholder: "order_delivered_update" }), _jsx(Input, { label: "Order cancelled template name", value: waTemplateOrderCancelled, onChange: (e) => setWaTemplateOrderCancelled(e.target.value), placeholder: "order_cancelled_update" }), _jsx(Input, { label: "Refund initiated template name", value: waTemplateRefundInitiated, onChange: (e) => setWaTemplateRefundInitiated(e.target.value), placeholder: "refund_initiated_update" })] })] }) }), _jsx(TabsContent, { value: "notifications", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "lg", children: [_jsx(Text, { size: "xs", color: "muted", children: "In-app notifications are always on. Enable external channels below to let the platform fan out to email, WhatsApp, or SMS. Users can further restrict which types they receive." }), _jsxs(Stack, { gap: "xs", surface: "muted", rounded: "lg", border: "default", padding: "md", children: [_jsxs(Row, { justify: "between", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "In-app (notification bell)" }), _jsx(Span, { color: "success", surface: "success-surface", size: "xs", weight: "semibold", rounded: "full", padding: "pill-xs", children: "Always on" })] }), _jsx(Text, { size: "xs", color: "muted", children: "Displayed in the notification bell and inbox. Cannot be disabled." })] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "Email notifications", checked: notifEmailEnabled, onChange: setNotifEmailEnabled }), notifEmailEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send email", options: PRIORITY_OPTIONS, value: notifEmailMinPriority, onValueChange: setNotifEmailMinPriority }), _jsx(Text, { size: "xs", weight: "medium", color: "muted", className: "pt-[0.25rem]", children: "Resend API (for transactional email)" }), _jsx(MaskedInput, { label: "Resend API Key", value: resendApiKey, onChange: setResendApiKey, placeholder: "re_live_\u2026", helperText: "Get your key at resend.com \u2014 used for all transactional notifications." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "From email", value: notifFromEmail, onChange: (e) => setNotifFromEmail(e.target.value), placeholder: "noreply@letitrip.in", type: "email" }), _jsx(Input, { label: "From name", value: notifFromName, onChange: (e) => setNotifFromName(e.target.value), placeholder: "LetItRip" })] }), _jsx(Text, { size: "xs", weight: "medium", color: "muted", className: "pt-[0.25rem]", children: "Notification types (leave empty to send all types)" }), _jsx(PaginatedSelect, { multiple: true, options: NOTIF_TYPE_OPTIONS, value: notifEmailTypes, onChange: (values) => setNotifEmailTypes(values), placeholder: "All notification types" })] }))] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "WhatsApp notifications", checked: notifWhatsappEnabled, onChange: setNotifWhatsappEnabled }), notifWhatsappEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send WhatsApp", options: PRIORITY_OPTIONS, value: notifWhatsappMinPriority, onValueChange: setNotifWhatsappMinPriority }), _jsx(Toggle, { label: "Enable WhatsApp OTP (for login and verification)", checked: notifWhatsappOtpEnabled, onChange: setNotifWhatsappOtpEnabled }), _jsx(Text, { size: "xs", color: "muted", children: "WhatsApp credentials are configured in the WhatsApp tab (\u246C). OTP messages use the same phone number." }), _jsx(Text, { size: "xs", weight: "medium", color: "muted", className: "pt-[0.25rem]", children: "Notification types (leave empty to send all types)" }), _jsx(PaginatedSelect, { multiple: true, options: NOTIF_TYPE_OPTIONS, value: notifWhatsappTypes, onChange: (values) => setNotifWhatsappTypes(values), placeholder: "All notification types" })] }))] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "SMS notifications", checked: notifSmsEnabled, onChange: setNotifSmsEnabled }), notifSmsEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send SMS", options: PRIORITY_OPTIONS, value: notifSmsMinPriority, onValueChange: setNotifSmsMinPriority }), _jsx(Text, { size: "xs", color: "muted", children: "SMS gateway credentials (e.g. Twilio, MSG91) can be configured in the Integrations tab once an SMS provider is connected." })] }))] })] }) }), _jsx(TabsContent, { value: "legal", children: _jsx(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [
678
+ ]), children: "+ Add tier" })] }), _jsx(Input, { label: "Auto-extend window (minutes before end)", value: String(autoExtendWindow), onChange: (e) => setAutoExtendWindow(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Extend auction end time if a bid arrives within this window." }), _jsx(Input, { label: "Settlement grace period (hours)", value: String(settlementGrace), onChange: (e) => setSettlementGrace(parseInt(e.target.value) || 0), type: "number", min: 1, helperText: "Time winner has to pay before the auction is re-listed." })] }) }), _jsx(TabsContent, { value: "limits", children: _jsx(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Max products per store", value: String(maxProductsPerStore), onChange: (e) => setMaxProductsPerStore(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max images per product", value: String(maxImagesPerProduct), onChange: (e) => setMaxImagesPerProduct(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max video size (MB)", value: String(maxVideoSizeMb), onChange: (e) => setMaxVideoSizeMb(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max custom fields per product", value: String(maxCustomFields), onChange: (e) => setMaxCustomFields(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Max custom sections per product", value: String(maxCustomSections), onChange: (e) => setMaxCustomSections(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Order cancellation window (hours)", value: String(orderCancelWindow), onChange: (e) => setOrderCancelWindow(parseInt(e.target.value) || 0), type: "number", min: 0 })] }) }) }), _jsx(TabsContent, { value: "whatsapp", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Platform-level WhatsApp Business Cloud API credentials. Used for automated purchase announcements to admin numbers when orders are placed. Store owners configure their own credentials in Store \u2192 WhatsApp." }), _jsx(Input, { label: "Phone Number ID", value: waPhoneNumberId, onChange: (e) => setWaPhoneNumberId(e.target.value), placeholder: "987654321098765", helperText: "From Meta for Developers \u2192 App \u2192 WhatsApp \u2192 API Setup" }), _jsx(MaskedInput, { label: "Cloud API System User Token", value: waCloudApiToken, onChange: setWaCloudApiToken, placeholder: "EAAxxxxxxxx\u2026", helperText: "Long-lived system user access token with WhatsApp Business permissions" }), _jsx(Input, { label: "Admin Notify Numbers", value: waAdminNotifyNumbers, onChange: (e) => setWaAdminNotifyNumbers(e.target.value), placeholder: "919876543210,918765432109", helperText: "Comma-separated, digits-only, include country code. These receive a WhatsApp message when any order is placed." }), _jsx(Text, { size: "xs", color: "muted", children: "Approved Meta message templates for the buyer-facing WhatsApp order-updates addon (Fees tab). Meta rejects business-initiated free-text WhatsApp messages outside a 24h customer-service window \u2014 a real template name here is required for that notification type to actually deliver. Leave blank to skip WhatsApp for that type (in-app + email still fire)." }), _jsx(Input, { label: "Template language code", value: waTemplateLanguage, onChange: (e) => setWaTemplateLanguage(e.target.value), placeholder: "en", helperText: "BCP-47 code the templates below were approved in, e.g. en or en_US." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Order placed template name", value: waTemplateOrderPlaced, onChange: (e) => setWaTemplateOrderPlaced(e.target.value), placeholder: "order_placed_update" }), _jsx(Input, { label: "Order confirmed template name", value: waTemplateOrderConfirmed, onChange: (e) => setWaTemplateOrderConfirmed(e.target.value), placeholder: "order_confirmed_update" }), _jsx(Input, { label: "Order shipped template name", value: waTemplateOrderShipped, onChange: (e) => setWaTemplateOrderShipped(e.target.value), placeholder: "order_shipped_update" }), _jsx(Input, { label: "Order delivered template name", value: waTemplateOrderDelivered, onChange: (e) => setWaTemplateOrderDelivered(e.target.value), placeholder: "order_delivered_update" }), _jsx(Input, { label: "Order cancelled template name", value: waTemplateOrderCancelled, onChange: (e) => setWaTemplateOrderCancelled(e.target.value), placeholder: "order_cancelled_update" }), _jsx(Input, { label: "Refund initiated template name", value: waTemplateRefundInitiated, onChange: (e) => setWaTemplateRefundInitiated(e.target.value), placeholder: "refund_initiated_update" })] })] }) }), _jsx(TabsContent, { value: "notifications", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "lg", children: [_jsx(Text, { size: "xs", color: "muted", children: "In-app notifications are always on. Enable external channels below to let the platform fan out to email, WhatsApp, or SMS. Users can further restrict which types they receive." }), _jsxs(Stack, { gap: "xs", surface: "muted", rounded: "lg", border: "default", padding: "md", children: [_jsxs(Row, { justify: "between", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "In-app (notification bell)" }), _jsx(Span, { color: "success", surface: "success-surface", size: "xs", weight: "semibold", rounded: "full", padding: "pill-xs", children: "Always on" })] }), _jsx(Text, { size: "xs", color: "muted", children: "Displayed in the notification bell and inbox. Cannot be disabled." })] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "Email notifications", checked: notifEmailEnabled, onChange: setNotifEmailEnabled }), notifEmailEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send email", options: PRIORITY_OPTIONS, value: notifEmailMinPriority, onValueChange: setNotifEmailMinPriority }), _jsx(Text, { size: "xs", weight: "medium", color: "muted", className: "pt-[0.25rem]", children: "Resend API (for transactional email)" }), _jsx(MaskedInput, { label: "Resend API Key", value: resendApiKey, onChange: setResendApiKey, placeholder: "re_live_\u2026", helperText: "Get your key at resend.com \u2014 used for all transactional notifications." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "From email", value: notifFromEmail, onChange: (e) => setNotifFromEmail(e.target.value), placeholder: "noreply@letitrip.in", type: "email" }), _jsx(Input, { label: "From name", value: notifFromName, onChange: (e) => setNotifFromName(e.target.value), placeholder: "LetItRip" })] }), _jsx(Text, { size: "xs", weight: "medium", color: "muted", className: "pt-[0.25rem]", children: "Notification types (leave empty to send all types)" }), _jsx(PaginatedSelect, { multiple: true, options: NOTIF_TYPE_OPTIONS, value: notifEmailTypes, onChange: (values) => setNotifEmailTypes(values), placeholder: "All notification types" })] }))] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "Daily status digest", checked: digestEnabled, onChange: setDigestEnabled }), digestEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Text, { size: "xs", color: "muted", children: "Sent every morning at 10:00 IST with the previous 24 hours of order activity. Its arrival doubles as the platform health check \u2014 if it stops landing, something needs a look." }), _jsx(Textarea, { label: "Recipients (To)", value: digestRecipients, onChange: (e) => setDigestRecipients(e.target.value), placeholder: "support@letitrip.in\nmohasin@letitrip.in", rows: 4, helperText: "One address per line." }), _jsx(Textarea, { label: "CC recipients", value: digestCcRecipients, onChange: (e) => setDigestCcRecipients(e.target.value), placeholder: "One address per line", rows: 3, helperText: "Optional \u2014 anyone else who should receive a copy." })] }))] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "WhatsApp notifications", checked: notifWhatsappEnabled, onChange: setNotifWhatsappEnabled }), notifWhatsappEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send WhatsApp", options: PRIORITY_OPTIONS, value: notifWhatsappMinPriority, onValueChange: setNotifWhatsappMinPriority }), _jsx(Toggle, { label: "Enable WhatsApp OTP (for login and verification)", checked: notifWhatsappOtpEnabled, onChange: setNotifWhatsappOtpEnabled }), _jsx(Text, { size: "xs", color: "muted", children: "WhatsApp credentials are configured in the WhatsApp tab (\u246C). OTP messages use the same phone number." }), _jsx(Text, { size: "xs", weight: "medium", color: "muted", className: "pt-[0.25rem]", children: "Notification types (leave empty to send all types)" }), _jsx(PaginatedSelect, { multiple: true, options: NOTIF_TYPE_OPTIONS, value: notifWhatsappTypes, onChange: (values) => setNotifWhatsappTypes(values), placeholder: "All notification types" })] }))] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "SMS notifications", checked: notifSmsEnabled, onChange: setNotifSmsEnabled }), notifSmsEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send SMS", options: PRIORITY_OPTIONS, value: notifSmsMinPriority, onValueChange: setNotifSmsMinPriority }), _jsx(Text, { size: "xs", color: "muted", children: "SMS gateway credentials (e.g. Twilio, MSG91) can be configured in the Integrations tab once an SMS provider is connected." })] }))] })] }) }), _jsx(TabsContent, { value: "legal", children: _jsx(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [
660
679
  ["Terms of Service", termsHtml, setTermsHtml],
661
680
  ["Privacy Policy", privacyHtml, setPrivacyHtml],
662
681
  ["Refund Policy", refundHtml, setRefundHtml],
@@ -1,9 +1,11 @@
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 { sortBy } from "@mohasinac/appkit/client";
4
+ import { useState } from "react";
4
5
  import { toRecordArray, toStringValue, } from "../hooks/useAdminListingData";
5
6
  import { DataListingView } from "./DataListingView";
6
7
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
+ import { RecordDetailModal } from "../../../ui";
7
9
  const ADMIN_STORE_ADDRESSES_CONFIG = {
8
10
  portal: "admin",
9
11
  title: "Store Addresses",
@@ -34,10 +36,28 @@ const ADMIN_STORE_ADDRESSES_CONFIG = {
34
36
  .join(" · "),
35
37
  status: item.isDefault ? "default" : "standard",
36
38
  updatedAt: toStringValue(item.storeId, ""),
39
+ _raw: item,
37
40
  })),
38
41
  getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
39
42
  buildFilters: () => undefined,
40
43
  };
41
44
  export function AdminStoreAddressesView(_props) {
42
- return _jsx(DataListingView, { config: ADMIN_STORE_ADDRESSES_CONFIG });
45
+ const [selected, setSelected] = useState(null);
46
+ const raw = selected?._raw ?? {};
47
+ return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: { ...ADMIN_STORE_ADDRESSES_CONFIG, onRowClick: (row) => setSelected(row) } }), _jsx(RecordDetailModal, { isOpen: Boolean(selected), onClose: () => setSelected(null), title: toStringValue(raw.label, "Store Address"), badges: raw.isDefault
48
+ ? [{ label: "Default pickup location", variant: "info" }]
49
+ : undefined, fields: [
50
+ { label: "Store", value: toStringValue(raw.storeName ?? raw.storeId, "—") },
51
+ { label: "Contact", value: toStringValue(raw.fullName, "—") },
52
+ { label: "Phone", value: toStringValue(raw.phone, "—") },
53
+ { label: "Address", value: toStringValue(raw.addressLine1, "—") },
54
+ ...(raw.addressLine2
55
+ ? [{ label: "Address line 2", value: toStringValue(raw.addressLine2, "—") }]
56
+ : []),
57
+ ...(raw.landmark ? [{ label: "Landmark", value: toStringValue(raw.landmark, "—") }] : []),
58
+ { label: "City", value: toStringValue(raw.city, "—") },
59
+ { label: "State", value: toStringValue(raw.state, "—") },
60
+ { label: "Postal code", value: toStringValue(raw.postalCode ?? raw.pincode, "—") },
61
+ { label: "Country", value: toStringValue(raw.country, "India") },
62
+ ] })] }));
43
63
  }
@@ -4,9 +4,11 @@ import { useApiMutation } from "@mohasinac/appkit/client";
4
4
  import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit/client";
5
5
  import { sortBy } from "@mohasinac/appkit/client";
6
6
  import React, { useState } from "react";
7
+ import { useRouter } from "next/navigation";
7
8
  import { useQueryClient } from "@tanstack/react-query";
8
9
  import { FilterChipGroup, ListingLayout, RowActionMenu, useToast } from "../../../ui";
9
10
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
11
+ import { ROUTES } from "../../../next/routing/route-map";
10
12
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
11
13
  import { ADMIN_BULK_ACTIONS, ROW_ACTION_META, ROW_ACTION_ID } from "../../products/constants/action-defs";
12
14
  import { ADMIN_STORE_STATUS_TABS } from "../constants/filter-tabs";
@@ -18,6 +20,7 @@ export function AdminStoresView({ children, ...props }) {
18
20
  const [editorRow, setEditorRow] = useState(null);
19
21
  const toast = useToast();
20
22
  const queryClient = useQueryClient();
23
+ const router = useRouter();
21
24
  const verifyStore = useApiMutation({
22
25
  mutationFn: (storeId) => apiClient.patch(ADMIN_ENDPOINTS.STORE_BY_ID(storeId), { isVerified: true }),
23
26
  onSuccess: () => {
@@ -96,6 +99,10 @@ export function AdminStoresView({ children, ...props }) {
96
99
  label: ACTIONS.ADMIN["manage-store"].label,
97
100
  onClick: () => setEditorRow(row),
98
101
  },
102
+ {
103
+ label: "Open full page",
104
+ onClick: () => router.push(String(ROUTES.ADMIN.STORE_DETAIL(row.id))),
105
+ },
99
106
  {
100
107
  label: ACTIONS.ADMIN["verify-store"].label,
101
108
  onClick: () => verifyStore.mutate(row.id),
@@ -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, useToast, } from "../../../ui";
9
- import { FieldInput } from "../../../ui/forms";
9
+ import { FieldInput, FormErrorSummary } from "../../../ui/forms";
10
10
  const sublistingCategoryFormSchema = z.object({
11
11
  name: z.string().min(1, "Name is required").max(120),
12
12
  slug: z.string().regex(/^[a-z0-9-]+$/, "Lowercase letters, digits and hyphens only").optional().or(z.literal("")),
@@ -81,7 +81,7 @@ export function AdminSublistingCategoryEditorView({ categoryId, onSaved, onDelet
81
81
  _jsx(Form, { schema: sublistingCategoryFormSchema, 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: "Category name", value: name, onChange: (v) => setName(v), required: true, placeholder: "e.g. Base Set Charizard 108/120" }), _jsx(Input, { label: "Item code", value: itemCode, onChange: (e) => setItemCode(e.target.value), placeholder: "e.g. PSA 10, 108/120, STH", helperText: "Grade, card number, or series code. Optional." })] }), _jsx(Input, { label: "Description", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Brief description shown on the public category page" }), _jsx(ImageUpload, { label: "Cover image", currentImage: coverImage, onUpload: (file) => upload(file, "sublisting-categories", true, {
82
82
  type: "category-image",
83
83
  name: name || "sublisting",
84
- }), onChange: setCoverImage }), _jsxs(Row, { gap: "3", padding: "t-xs", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !name || isSubmitting, onClick: () => {
84
+ }), onChange: setCoverImage }), _jsx(FormErrorSummary, {}), _jsxs(Row, { gap: "3", padding: "t-xs", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !name || isSubmitting, onClick: () => {
85
85
  clearErrors();
86
86
  if (!name.trim()) {
87
87
  setFieldError("name", "Category name is required");