@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
@@ -125,8 +125,16 @@ export interface CategoryDocument extends BaseDocument {
125
125
  brandCountry?: string;
126
126
  /** Brand founding year — categoryType==="brand". */
127
127
  brandFounded?: number;
128
- /** Banner image for the brand storefront page — categoryType==="brand". */
129
- brandBannerImage?: string;
128
+ /**
129
+ * Short bullet points — "why shop this category/brand." Category and
130
+ * brand detail pages only; renders nothing when absent/empty.
131
+ */
132
+ highlights?: string[];
133
+ /** A handful of Q&A pairs shown on the category/brand detail page. */
134
+ faqs?: {
135
+ question: string;
136
+ answer: string;
137
+ }[];
130
138
  /**
131
139
  * "special" — admin-curated bundle; writes reverse `partOfBundleIds`
132
140
  * pointers on member products and shows the "Bundled" badge on cards.
@@ -135,6 +143,15 @@ export interface CategoryDocument extends BaseDocument {
135
143
  * Does NOT update `partOfBundleIds`; used as a discovery surface only.
136
144
  */
137
145
  bundleKind?: "special" | "brand";
146
+ /**
147
+ * Which brand this bundle belongs to, for the brand detail page's bundle
148
+ * tab — works for both `bundleKind:"special"` (static) and `"brand"`
149
+ * (dynamic) bundles. Distinct from `bundleQueryRule.filter.brandSlug`,
150
+ * which is a dynamic-rule query *filter* (only meaningful when
151
+ * `bundleQueryRule.type==="dynamic"`) — this field is the bundle's own
152
+ * brand tag regardless of how its members are resolved.
153
+ */
154
+ brandSlug?: string;
138
155
  /** Discounted bundle price in decimal rupees. */
139
156
  bundlePrice?: number;
140
157
  /** Rule resolving the bundle's member products — static list or live query. */
@@ -229,8 +229,19 @@ export declare const categoryFirestoreSchema: z.ZodObject<{
229
229
  brandWebsite: z.ZodOptional<z.ZodString>;
230
230
  brandCountry: z.ZodOptional<z.ZodString>;
231
231
  brandFounded: z.ZodOptional<z.ZodNumber>;
232
- brandBannerImage: z.ZodOptional<z.ZodString>;
232
+ highlights: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
233
+ faqs: z.ZodOptional<z.ZodArray<z.ZodObject<{
234
+ question: z.ZodString;
235
+ answer: z.ZodString;
236
+ }, "strip", z.ZodTypeAny, {
237
+ question: string;
238
+ answer: string;
239
+ }, {
240
+ question: string;
241
+ answer: string;
242
+ }>, "many">>;
233
243
  bundleKind: z.ZodOptional<z.ZodEnum<["special", "brand"]>>;
244
+ brandSlug: z.ZodOptional<z.ZodString>;
234
245
  bundlePrice: z.ZodOptional<z.ZodEffects<z.ZodNumber, number, number>>;
235
246
  bundleQueryRule: z.ZodOptional<z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
236
247
  type: z.ZodLiteral<"static">;
@@ -419,7 +430,12 @@ export declare const categoryFirestoreSchema: z.ZodObject<{
419
430
  name: string;
420
431
  tier: number;
421
432
  }[];
433
+ faqs?: {
434
+ question: string;
435
+ answer: string;
436
+ }[] | undefined;
422
437
  description?: string | undefined;
438
+ brandSlug?: string | undefined;
423
439
  showOnHomepage?: boolean | undefined;
424
440
  categoryType?: "brand" | "category" | "sublisting" | "bundle" | undefined;
425
441
  bundleKind?: "brand" | "special" | undefined;
@@ -442,7 +458,7 @@ export declare const categoryFirestoreSchema: z.ZodObject<{
442
458
  brandWebsite?: string | undefined;
443
459
  brandCountry?: string | undefined;
444
460
  brandFounded?: number | undefined;
445
- brandBannerImage?: string | undefined;
461
+ highlights?: string[] | undefined;
446
462
  bundlePrice?: number | undefined;
447
463
  bundleQueryRule?: {
448
464
  type: "static";
@@ -513,7 +529,12 @@ export declare const categoryFirestoreSchema: z.ZodObject<{
513
529
  name: string;
514
530
  tier: number;
515
531
  }[];
532
+ faqs?: {
533
+ question: string;
534
+ answer: string;
535
+ }[] | undefined;
516
536
  description?: string | undefined;
537
+ brandSlug?: string | undefined;
517
538
  showOnHomepage?: boolean | undefined;
518
539
  categoryType?: "brand" | "category" | "sublisting" | "bundle" | undefined;
519
540
  bundleKind?: "brand" | "special" | undefined;
@@ -536,7 +557,7 @@ export declare const categoryFirestoreSchema: z.ZodObject<{
536
557
  brandWebsite?: string | undefined;
537
558
  brandCountry?: string | undefined;
538
559
  brandFounded?: number | undefined;
539
- brandBannerImage?: string | undefined;
560
+ highlights?: string[] | undefined;
540
561
  bundlePrice?: number | undefined;
541
562
  bundleQueryRule?: {
542
563
  type: "static";
@@ -76,8 +76,10 @@ export const categoryFirestoreSchema = z.object({
76
76
  brandWebsite: z.string().optional(),
77
77
  brandCountry: z.string().optional(),
78
78
  brandFounded: z.number().int().optional(),
79
- brandBannerImage: z.string().optional(),
79
+ highlights: z.array(z.string()).optional(),
80
+ faqs: z.array(z.object({ question: z.string(), answer: z.string() })).optional(),
80
81
  bundleKind: z.enum(["special", "brand"]).optional(),
82
+ brandSlug: z.string().optional(),
81
83
  bundlePrice: rupeesSchema.optional(),
82
84
  bundleQueryRule: bundleQueryRuleSchema.optional(),
83
85
  bundleStockStatus: z.enum(["in_stock", "out_of_stock"]).optional(),
@@ -37,6 +37,13 @@ export interface CategoryItem {
37
37
  brandCountry?: string;
38
38
  /** Brand founded year — only set when categoryType==="brand". */
39
39
  brandFounded?: number;
40
+ /** Short bullet points — "why shop this category/brand." */
41
+ highlights?: string[];
42
+ /** A handful of Q&A pairs shown on the category/brand detail page. */
43
+ faqs?: {
44
+ question: string;
45
+ answer: string;
46
+ }[];
40
47
  name: string;
41
48
  slug: string;
42
49
  description?: string;
@@ -6,9 +6,23 @@
6
6
  * purpose from `checkout-actions.ts`'s third-party shipping consent OTP —
7
7
  * same crypto primitives, separate Firestore namespace and copy.
8
8
  */
9
- /** Sends a checkout-value OTP to the buyer's registered email. Returns masked email so the UI can confirm which inbox to check. */
10
- export declare function sendCheckoutValueOtp(userId: string, userEmail: string): Promise<{
11
- maskedEmail: string;
9
+ export type CheckoutValueOtpChannel = "email" | "whatsapp";
10
+ /**
11
+ * Sends a checkout-value OTP either by email (default) or WhatsApp
12
+ * (opt-in — the buyer must explicitly choose it in the UI). WhatsApp phone
13
+ * is always resolved server-side from the selected shipping address —
14
+ * never trust a client-supplied phone number.
15
+ *
16
+ * If the chosen channel (and, for email, its skip-worthy fallback) is
17
+ * currently circuit-broken or unconfigured, the OTP gate is treated as
18
+ * satisfied (`skipped: true`) rather than blocking checkout — the caller
19
+ * must log this, since it is a security-relevant bypass even though
20
+ * user-authorized by the channel outage.
21
+ */
22
+ export declare function sendCheckoutValueOtp(userId: string, userEmail: string, channel?: CheckoutValueOtpChannel, addressId?: string): Promise<{
23
+ maskedEmail?: string;
24
+ maskedPhone?: string;
25
+ skipped?: boolean;
12
26
  }>;
13
27
  /** Verifies the 6-digit checkout-value OTP and marks the Firestore record verified. */
14
28
  export declare function verifyCheckoutValueOtp(userId: string, code: string): Promise<void>;
@@ -8,18 +8,94 @@
8
8
  */
9
9
  import { timingSafeEqual } from "crypto";
10
10
  import { ValidationError } from "../../../errors";
11
+ import { normalizeError } from "../../../errors/normalize";
11
12
  import { serverLogger } from "../../../monitoring";
12
13
  import { sendEmail } from "../../contact/email";
13
14
  import { getAdminDb } from "../../../providers/db-firebase";
14
15
  import { resolveDate } from "../../../utils";
15
- import { CHECKOUT_VALUE_OTP_EXPIRY_MS, CHECKOUT_VALUE_OTP_MAX_ATTEMPTS, hashOtp, generateOtpCode, checkoutValueOtpRef, enforceCheckoutValueOtpRateLimit, } from "../../auth/checkout-value-otp";
16
- /** Sends a checkout-value OTP to the buyer's registered email. Returns masked email so the UI can confirm which inbox to check. */
17
- export async function sendCheckoutValueOtp(userId, userEmail) {
16
+ import { addressesRepository } from "../../addresses/repository/addresses.repository";
17
+ import { siteSettingsRepository } from "../../admin/repository/site-settings.repository";
18
+ import { sendWhatsAppBusinessMessage } from "../../whatsapp-bot/helpers/whatsapp";
19
+ import { isChannelHealthy, withChannelRetry } from "../../../_internal/server/notifications/channel-health";
20
+ import { CHECKOUT_VALUE_OTP_EXPIRY_MS, CHECKOUT_VALUE_OTP_MAX_ATTEMPTS, hashOtp, generateOtpCode, checkoutValueOtpRef, enforceCheckoutValueOtpRateLimit, buildCheckoutValueOtpMessage, } from "../../auth/checkout-value-otp";
21
+ function maskEmail(userEmail) {
22
+ const [local, domain] = userEmail.split("@");
23
+ return domain
24
+ ? `${local.length <= 2 ? "*".repeat(local.length) : local[0] + "*".repeat(local.length - 2) + local[local.length - 1]}@${domain}`
25
+ : "***";
26
+ }
27
+ /**
28
+ * Sends a checkout-value OTP either by email (default) or WhatsApp
29
+ * (opt-in — the buyer must explicitly choose it in the UI). WhatsApp phone
30
+ * is always resolved server-side from the selected shipping address —
31
+ * never trust a client-supplied phone number.
32
+ *
33
+ * If the chosen channel (and, for email, its skip-worthy fallback) is
34
+ * currently circuit-broken or unconfigured, the OTP gate is treated as
35
+ * satisfied (`skipped: true`) rather than blocking checkout — the caller
36
+ * must log this, since it is a security-relevant bypass even though
37
+ * user-authorized by the channel outage.
38
+ */
39
+ export async function sendCheckoutValueOtp(userId, userEmail, channel = "email", addressId) {
18
40
  const db = getAdminDb();
19
41
  await enforceCheckoutValueOtpRateLimit(db, userId);
20
42
  const code = generateOtpCode();
21
43
  const codeHash = hashOtp(code);
22
44
  const expiresAt = new Date(Date.now() + CHECKOUT_VALUE_OTP_EXPIRY_MS);
45
+ const siteName = process.env.NEXT_PUBLIC_SITE_NAME || "LetItRip";
46
+ if (channel === "whatsapp") {
47
+ if (!addressId) {
48
+ throw new ValidationError("Select a delivery address before sending the code via WhatsApp.");
49
+ }
50
+ const settings = await siteSettingsRepository.getSingleton().catch(() => null);
51
+ const creds = settings?.credentials ?? {};
52
+ const whatsappOtpEnabled = settings?.notificationChannels?.whatsapp?.otpEnabled === true;
53
+ const phoneNumberId = creds.whatsappPhoneNumberId?.trim() ?? "";
54
+ const accessToken = creds.whatsappCloudApiToken?.trim() ?? "";
55
+ const credsReady = phoneNumberId && accessToken && !phoneNumberId.includes("PLACEHOLDER") && !accessToken.includes("PLACEHOLDER");
56
+ if (!whatsappOtpEnabled || !credsReady) {
57
+ throw new ValidationError("WhatsApp verification isn't available right now — send the code by email instead.");
58
+ }
59
+ const address = await addressesRepository.findById(addressId);
60
+ const phone = address?.phone?.trim();
61
+ if (!phone) {
62
+ throw new ValidationError("The selected address has no phone number on file — send the code by email instead.");
63
+ }
64
+ if (!(await isChannelHealthy("whatsapp"))) {
65
+ serverLogger.warn("sendCheckoutValueOtp: WhatsApp channel unhealthy — skipping OTP gate", { uid: userId, channel });
66
+ return { skipped: true };
67
+ }
68
+ await checkoutValueOtpRef(db, userId).set({
69
+ codeHash,
70
+ expiresAt,
71
+ attempts: 0,
72
+ verified: false,
73
+ createdAt: new Date(),
74
+ });
75
+ const message = buildCheckoutValueOtpMessage(code, siteName);
76
+ try {
77
+ const sent = await withChannelRetry("whatsapp", async () => {
78
+ const ok = await sendWhatsAppBusinessMessage({ toPhone: phone, message, phoneNumberId, accessToken });
79
+ if (!ok)
80
+ throw new Error("WhatsApp Cloud API returned a non-OK response");
81
+ return ok;
82
+ });
83
+ if (!sent)
84
+ throw new Error("WhatsApp send failed");
85
+ }
86
+ catch (err) {
87
+ void normalizeError(err);
88
+ serverLogger.error("Failed to send checkout value OTP via WhatsApp", { uid: userId, err });
89
+ throw new ValidationError("We couldn't send the verification code via WhatsApp — please try again, or send it by email instead.");
90
+ }
91
+ const maskedPhone = phone.length > 4 ? `${"*".repeat(phone.length - 4)}${phone.slice(-4)}` : "***";
92
+ serverLogger.info(`Checkout value OTP sent via WhatsApp: uid=${userId}`);
93
+ return { maskedPhone };
94
+ }
95
+ if (!(await isChannelHealthy("email"))) {
96
+ serverLogger.warn("sendCheckoutValueOtp: email channel unhealthy — skipping OTP gate", { uid: userId, channel });
97
+ return { skipped: true };
98
+ }
23
99
  await checkoutValueOtpRef(db, userId).set({
24
100
  codeHash,
25
101
  expiresAt,
@@ -27,24 +103,27 @@ export async function sendCheckoutValueOtp(userId, userEmail) {
27
103
  verified: false,
28
104
  createdAt: new Date(),
29
105
  });
30
- const siteName = process.env.NEXT_PUBLIC_SITE_NAME || "LetItRip";
31
- const [local, domain] = userEmail.split("@");
32
- const maskedEmail = domain
33
- ? `${local.length <= 2 ? "*".repeat(local.length) : local[0] + "*".repeat(local.length - 2) + local[local.length - 1]}@${domain}`
34
- : "***";
35
- const { error } = await sendEmail({
36
- to: userEmail,
37
- subject: `${siteName}: Verify your order`,
38
- html: `
39
- <div style="font-family:sans-serif;max-width:480px;margin:0 auto;padding:24px">
40
- <h2 style="margin-bottom:8px">Verify Your Order</h2>
41
- <p style="color:#555">Your cart total requires verification before checkout. Enter this code to continue:</p>
42
- <div style="font-size:36px;font-weight:bold;letter-spacing:8px;text-align:center;margin:24px 0;padding:16px;background:#f3f4f6;border-radius:8px">${code}</div>
43
- <p style="color:#888;font-size:12px">This code expires in 10 minutes. If you did not request this, please ignore this email.</p>
44
- </div>
45
- `,
46
- });
47
- if (error) {
106
+ const maskedEmail = maskEmail(userEmail);
107
+ try {
108
+ await withChannelRetry("email", async () => {
109
+ const { error } = await sendEmail({
110
+ to: userEmail,
111
+ subject: `${siteName}: Verify your order`,
112
+ html: `
113
+ <div style="font-family:sans-serif;max-width:480px;margin:0 auto;padding:24px">
114
+ <h2 style="margin-bottom:8px">Verify Your Order</h2>
115
+ <p style="color:#555">Your cart total requires verification before checkout. Enter this code to continue:</p>
116
+ <div style="font-size:36px;font-weight:bold;letter-spacing:8px;text-align:center;margin:24px 0;padding:16px;background:#f3f4f6;border-radius:8px">${code}</div>
117
+ <p style="color:#888;font-size:12px">This code expires in 10 minutes. If you did not request this, please ignore this email.</p>
118
+ </div>
119
+ `,
120
+ });
121
+ if (error)
122
+ throw new Error(String(error));
123
+ });
124
+ }
125
+ catch (err) {
126
+ void normalizeError(err);
48
127
  // Root-caused 2026-08-20: this used to be fire-and-forget — the
49
128
  // Firestore OTP doc was written and the caller always got back a
50
129
  // success response ("code sent") regardless of whether the email
@@ -52,7 +131,7 @@ export async function sendCheckoutValueOtp(userId, userEmail) {
52
131
  // unverified sending domain, outage) silently stranded the buyer
53
132
  // with a checkout they could never complete, since the OTP gate
54
133
  // gave no indication anything had gone wrong.
55
- serverLogger.error("Failed to send checkout value OTP email", { uid: userId, error });
134
+ serverLogger.error("Failed to send checkout value OTP email", { uid: userId, err });
56
135
  throw new ValidationError("We couldn't send the verification code — please try again in a moment, or contact support if this keeps happening.");
57
136
  }
58
137
  serverLogger.info(`Checkout value OTP sent: uid=${userId}`);
@@ -1,8 +1,16 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { normalizeError } from "../../../errors/normalize";
4
- import { useState, useCallback } from "react";
4
+ import { useEffect, useState, useCallback } from "react";
5
+ import { z } from "zod";
5
6
  import { Button, Div, Form, Heading, Input, Span, Stack, Text, Textarea } from "../../../ui";
7
+ import { useFormShellState, FormShellContext, FormErrorSummary } from "../../../ui/forms";
8
+ const contactFormSchema = z.object({
9
+ name: z.string().min(1, "Name is required"),
10
+ email: z.string().email("Enter a valid email address"),
11
+ subject: z.string().min(1, "Subject is required"),
12
+ message: z.string().min(10, "Message must be at least 10 characters"),
13
+ });
6
14
  const CLS_FIELD_ERROR = "text-error";
7
15
  const CLS_ERROR_BANNER = "rounded-lg bg-error-surface dark:bg-error-surface border border-error dark:border-error px-[var(--appkit-space-4)] py-[var(--appkit-space-3)] text-[length:var(--appkit-text-sm)] text-error dark:text-error";
8
16
  const INITIAL_FORM = {
@@ -17,6 +25,11 @@ export function ContactForm({ onSubmit, labels = {}, className = "", }) {
17
25
  const [submitted, setSubmitted] = useState(false);
18
26
  const [errors, setErrors] = useState({});
19
27
  const [submitError, setSubmitError] = useState(null);
28
+ const { shellCtx, validate: validateSchema } = useFormShellState(contactFormSchema);
29
+ useEffect(() => {
30
+ validateSchema(form);
31
+ // eslint-disable-next-line react-hooks/exhaustive-deps
32
+ }, [form, validateSchema]);
20
33
  const validate = () => {
21
34
  const newErrors = {};
22
35
  if (!form.name.trim())
@@ -56,7 +69,7 @@ export function ContactForm({ onSubmit, labels = {}, className = "", }) {
56
69
  return (_jsxs(Div, { className: `text-center ${className}`, padding: "y-3xl", children: [_jsx(Div, { textSize: "5xl", className: "mb-4", children: "\u2705" }), _jsx(Heading, { level: 2, className: "mb-2", children: labels.successTitle ?? "Message Sent!" }), _jsx(Text, { variant: "secondary", className: "mb-6", children: labels.successDescription ?? "We'll get back to you shortly." }), _jsx(Button, { type: "button", variant: "outline", onClick: () => setSubmitted(false), children: labels.sendAnotherButton ?? "Send another message" })] }));
57
70
  }
58
71
  const field = (id, label, placeholder, multiline) => (_jsxs(Stack, { gap: "xs", children: [_jsx("label", { htmlFor: id, className: "text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-text-muted)]", children: label }), multiline ? (_jsx(Textarea, { id: id, value: form[id], onChange: (e) => setForm((f) => ({ ...f, [id]: e.target.value })), placeholder: placeholder, rows: 5, className: "w-full rounded-lg border border-neutral-200 border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] outline-none focus:ring-2 ring-primary/20 transition" })) : (_jsx(Input, { id: id, type: id === "email" ? "email" : "text", value: form[id], onChange: (e) => setForm((f) => ({ ...f, [id]: e.target.value })), placeholder: placeholder, className: "h-10" })), errors[id] && _jsx(Span, { size: "xs", className: CLS_FIELD_ERROR, children: errors[id] })] }));
59
- return (_jsxs(Form, { onSubmit: handleSubmit, noValidate: true, className: className, spacing: "md", children: [_jsx(Heading, { level: 2, className: "mb-6", children: labels.title ?? "Send us a message" }), submitError && (_jsx(Div, { className: CLS_ERROR_BANNER, children: submitError })), field("name", labels.nameLabel ?? "Your Name", labels.namePlaceholder ?? "Enter your name"), field("email", labels.emailLabel ?? "Email", labels.emailPlaceholder ?? "your@email.com"), field("subject", labels.subjectLabel ?? "Subject", labels.subjectPlaceholder ?? "How can we help?"), field("message", labels.messageLabel ?? "Message", labels.messagePlaceholder ?? "Tell us more…", true), _jsx(Button, { type: "submit", variant: "primary", disabled: isSubmitting, className: "w-full sm:w-auto", children: isSubmitting
60
- ? (labels.submittingButton ?? "Sending…")
61
- : (labels.submitButton ?? "Send Message") })] }));
72
+ return (_jsx(FormShellContext.Provider, { value: shellCtx, children: _jsxs(Form, { onSubmit: handleSubmit, noValidate: true, className: className, spacing: "md", children: [_jsx(Heading, { level: 2, className: "mb-6", children: labels.title ?? "Send us a message" }), submitError && (_jsx(Div, { className: CLS_ERROR_BANNER, children: submitError })), field("name", labels.nameLabel ?? "Your Name", labels.namePlaceholder ?? "Enter your name"), field("email", labels.emailLabel ?? "Email", labels.emailPlaceholder ?? "your@email.com"), field("subject", labels.subjectLabel ?? "Subject", labels.subjectPlaceholder ?? "How can we help?"), field("message", labels.messageLabel ?? "Message", labels.messagePlaceholder ?? "Tell us more…", true), _jsx(FormErrorSummary, {}), _jsx(Button, { type: "submit", variant: "primary", disabled: isSubmitting, className: "w-full sm:w-auto", children: isSubmitting
73
+ ? (labels.submittingButton ?? "Sending…")
74
+ : (labels.submitButton ?? "Send Message") })] }) }));
62
75
  }
@@ -6,6 +6,7 @@ type SendEmailOptions = Record<string, JsonValue> & {
6
6
  html?: string;
7
7
  text?: string;
8
8
  from?: string;
9
+ cc?: string | string[];
9
10
  replyTo?: string;
10
11
  };
11
12
  export declare function sendEmail(opts: SendEmailOptions): Promise<{
@@ -33,6 +33,9 @@ export async function sendEmail(opts) {
33
33
  html: typeof opts.html === "string" ? opts.html : "",
34
34
  text: typeof opts.text === "string" ? opts.text : undefined,
35
35
  from: typeof opts.from === "string" ? opts.from : undefined,
36
+ cc: typeof opts.cc === "string" || Array.isArray(opts.cc)
37
+ ? opts.cc
38
+ : undefined,
36
39
  replyTo: typeof opts.replyTo === "string" ? opts.replyTo : undefined,
37
40
  headers: typeof opts.headers === "object" && opts.headers
38
41
  ? opts.headers
@@ -2,14 +2,44 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } 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 { Alert, Button, Div, Grid, Heading, Input, Label, RichTextEditor, Row, Select, Stack, StackedViewShell, TagInput, Text, Textarea, Toggle, } from "../../../ui";
9
+ import { FormShellContext, useFormShellState, applyZodIssues, FormErrorSummary } from "../../../ui/forms";
8
10
  import { apiClient } from "../../../http";
9
11
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
12
  import { StepForm } from "../../shell";
11
13
  import { ProductInlineSelect } from "../../seller/components/ProductInlineSelect";
12
14
  import { CouponInlineSelect } from "../../seller/components/CouponInlineSelect";
15
+ // Covers the cross-type required fields plus the two conditional rules the
16
+ // "Settings" step already hand-enforces (offer coupon/code, poll min-2
17
+ // options) — mirrored here as Zod so they're also live/summary-visible, not
18
+ // just a submit-time gate. The many other per-type fields (survey/feedback
19
+ // form-builder arrays, raffle/spin config) are deliberately left unvalidated
20
+ // — modeling their full conditional shape correctly would need much deeper
21
+ // per-type domain rules than this pass is scoped for.
22
+ const eventDraftSchema = z.object({
23
+ type: z.string(),
24
+ title: z.string().min(1, "Title is required"),
25
+ startsAt: z.string().min(1, "Start date is required"),
26
+ endsAt: z.string().min(1, "End date is required"),
27
+ couponId: z.string(),
28
+ displayCode: z.string(),
29
+ pollOptions: z.array(z.object({ id: z.string(), label: z.string() })),
30
+ }).passthrough().superRefine((v, ctx) => {
31
+ if (v.type === "offer") {
32
+ if (!v.couponId.trim()) {
33
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["couponId"], message: "Coupon is required for offer events" });
34
+ }
35
+ if (!v.displayCode.trim()) {
36
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["displayCode"], message: "Display code is required for offer events" });
37
+ }
38
+ }
39
+ if (v.type === "poll" && v.pollOptions.filter((o) => o.label.trim()).length < 2) {
40
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["pollOptions"], message: "Poll events require at least 2 options" });
41
+ }
42
+ });
13
43
  const __P = {
14
44
  p4: "p-[var(--appkit-space-4)]",
15
45
  };
@@ -202,6 +232,11 @@ export function AdminEventEditorView({ eventId, onSaved, embedded, allowedTypes,
202
232
  const [raffleWinnerName, setRaffleWinnerName] = React.useState("");
203
233
  const [raffleEntryCount, setRaffleEntryCount] = React.useState(null);
204
234
  const [raffleGithubUrl, setRaffleGithubUrl] = React.useState("");
235
+ const { shellCtx, setFieldError, validate } = useFormShellState(eventDraftSchema);
236
+ useEffect(() => {
237
+ validate(draft);
238
+ // eslint-disable-next-line react-hooks/exhaustive-deps
239
+ }, [draft, validate]);
205
240
  const update = React.useCallback((partial) => {
206
241
  setDraft((prev) => ({ ...prev, ...partial }));
207
242
  setSaveMessage(null);
@@ -295,6 +330,10 @@ export function AdminEventEditorView({ eventId, onSaved, embedded, allowedTypes,
295
330
  onSaved?.(savedId);
296
331
  },
297
332
  onError: (error) => {
333
+ const issues = error?.issues;
334
+ if (issues && issues.length > 0) {
335
+ applyZodIssues(issues, setFieldError);
336
+ }
298
337
  setSaveMessage(error instanceof Error ? error.message : "Save failed");
299
338
  },
300
339
  });
@@ -322,6 +361,7 @@ export function AdminEventEditorView({ eventId, onSaved, embedded, allowedTypes,
322
361
  const steps = [
323
362
  {
324
363
  label: "Details",
364
+ fields: ["type", "title", "slug", "description", "startsAt", "endsAt", "tags"],
325
365
  validate: (values) => {
326
366
  if (!values.title.trim())
327
367
  return "Title is required";
@@ -338,10 +378,12 @@ export function AdminEventEditorView({ eventId, onSaved, embedded, allowedTypes,
338
378
  },
339
379
  {
340
380
  label: "Media",
381
+ fields: ["coverImageUrl"],
341
382
  render: ({ values, onChange }) => (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Media" }), _jsx(Input, { label: "Cover Image URL (optional)", value: values.coverImageUrl, onChange: (e) => onChange({ coverImageUrl: e.target.value }), placeholder: "https://\u2026", helperText: "Direct link to the event cover image." })] })),
342
383
  },
343
384
  {
344
385
  label: "Settings",
386
+ fields: ["status", "allowGuestParticipation", "discountPercent", "saleBannerText", "couponId", "displayCode", "offerBannerText", "pollOptions", "resultsVisibility", "allowMultiSelect", "allowComment", "maxEntriesPerUser", "hasLeaderboard", "hasPointSystem", "pointsLabel", "entryReviewRequired", "surveyFields", "anonymous", "feedbackFields"],
345
387
  validate: (values) => {
346
388
  if (values.type === "offer" && !values.couponId.trim())
347
389
  return "Coupon ID is required for offer events";
@@ -355,6 +397,7 @@ export function AdminEventEditorView({ eventId, onSaved, embedded, allowedTypes,
355
397
  },
356
398
  {
357
399
  label: "Raffle / Spin",
400
+ fields: ["hasRaffle", "raffleType", "raffleTopN", "rafflePrize", "rafflePrizeCouponId", "rafflePrizeProductIds", "spinPrizes", "spinMaxPerUser", "spinWindowStart", "spinWindowEnd"],
358
401
  render: ({ values, onChange }) => {
359
402
  const isRaffleType = values.type === "raffle" || values.type === "spin_wheel";
360
403
  const showRaffleConfig = isRaffleType || values.hasRaffle;
@@ -364,7 +407,16 @@ export function AdminEventEditorView({ eventId, onSaved, embedded, allowedTypes,
364
407
  },
365
408
  ];
366
409
  const alertSection = eventQuery.error ? (_jsx(Alert, { variant: "error", title: "Could not load event", children: eventQuery.error instanceof Error ? eventQuery.error.message : "Unknown error" })) : null;
367
- const formContent = (_jsxs(Stack, { gap: "sm", children: [alertSection, _jsx(StepForm, { steps: steps, values: draft, onChange: update, onComplete: () => { saveMutation.mutate(); }, formId: "admin-event", currentStep: currentStep, onStepChange: setCurrentStep, completeLabel: isEdit ? "Save Changes" : "Create Event", isLoading: isLoading }), saveMessage && (_jsx(Alert, { variant: saveMessage.toLowerCase().includes("fail") || saveMessage.toLowerCase().includes("error") ? "error" : "success", title: "Save status", children: saveMessage }))] }));
410
+ const fieldToStepIndex = useMemo(() => {
411
+ const map = {};
412
+ steps.forEach((step, i) => {
413
+ step.fields?.forEach((field) => { map[field] = i; });
414
+ });
415
+ return map;
416
+ // eslint-disable-next-line react-hooks/exhaustive-deps
417
+ }, [steps.length]);
418
+ const wizardShellCtx = useMemo(() => ({ ...shellCtx, fieldToStepIndex, goToStep: (n) => setCurrentStep(n) }), [shellCtx, fieldToStepIndex, setCurrentStep]);
419
+ const formContent = (_jsxs(Stack, { gap: "sm", children: [alertSection, _jsxs(FormShellContext.Provider, { value: wizardShellCtx, children: [_jsx(FormErrorSummary, {}), _jsx(StepForm, { steps: steps, values: draft, onChange: update, onComplete: () => { saveMutation.mutate(); }, formId: "admin-event", currentStep: currentStep, onStepChange: setCurrentStep, completeLabel: isEdit ? "Save Changes" : "Create Event", isLoading: isLoading })] }), saveMessage && (_jsx(Alert, { variant: saveMessage.toLowerCase().includes("fail") || saveMessage.toLowerCase().includes("error") ? "error" : "success", title: "Save status", children: saveMessage }))] }));
368
420
  if (embedded) {
369
421
  return _jsx(Div, { className: `${__O.yAuto} ${__P.p4}`, children: formContent });
370
422
  }
@@ -1,22 +1,8 @@
1
1
  import React from "react";
2
- import type { StackedViewShellProps } from "../../../ui";
3
- export interface AdminEventEntriesViewProps extends Omit<StackedViewShellProps, "sections"> {
2
+ export interface AdminEventEntriesViewProps {
4
3
  eventId?: string;
5
4
  entriesEndpoint?: (eventId: string) => string;
6
5
  entryReviewEndpoint?: (eventId: string, entryId: string) => string;
7
6
  statsEndpoint?: (eventId: string) => string;
8
- children?: React.ReactNode;
9
- /** Back-link and event title header */
10
- renderHeader?: () => React.ReactNode;
11
- /** Stats banner (total / approved / flagged counts) */
12
- renderStats?: () => React.ReactNode;
13
- /** Filter bar (status dropdown, sort, search) */
14
- renderFilters?: () => React.ReactNode;
15
- /** Entries data table */
16
- renderTable?: () => React.ReactNode;
17
- /** Pagination footer */
18
- renderPagination?: () => React.ReactNode;
19
- /** Entry review side-drawer */
20
- renderReviewDrawer?: () => React.ReactNode;
21
7
  }
22
- export declare function AdminEventEntriesView({ eventId, entriesEndpoint, entryReviewEndpoint, statsEndpoint, renderHeader, renderStats, renderFilters, renderTable, renderPagination, renderReviewDrawer, ...rest }: AdminEventEntriesViewProps): React.JSX.Element;
8
+ export declare function AdminEventEntriesView({ eventId, entriesEndpoint, entryReviewEndpoint, statsEndpoint, }: AdminEventEntriesViewProps): React.JSX.Element;