@mohasinac/appkit 4.30.19 → 4.31.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 (417) hide show
  1. package/dist/_internal/client/features/layout/useSidebarSearch.d.ts +21 -5
  2. package/dist/_internal/client/features/layout/useSidebarSearch.js +27 -6
  3. package/dist/_internal/client/features/lottery/LotteryAdminEditView.d.ts +23 -3
  4. package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +106 -87
  5. package/dist/_internal/client/features/lottery/LotteryPullForm.d.ts +16 -5
  6. package/dist/_internal/client/features/lottery/LotteryPullForm.js +65 -40
  7. package/dist/_internal/server/features/auth/permissions.d.ts +2 -0
  8. package/dist/_internal/server/features/auth/permissions.js +3 -2
  9. package/dist/_internal/server/features/categories/data.js +15 -4
  10. package/dist/_internal/server/features/classified/index.d.ts +0 -1
  11. package/dist/_internal/server/features/classified/index.js +0 -1
  12. package/dist/_internal/server/features/digital-code/index.d.ts +0 -1
  13. package/dist/_internal/server/features/digital-code/index.js +0 -1
  14. package/dist/_internal/server/features/live/index.d.ts +0 -1
  15. package/dist/_internal/server/features/live/index.js +0 -1
  16. package/dist/_internal/server/features/products/data.js +15 -5
  17. package/dist/_internal/server/features/products/list-public.d.ts +9 -0
  18. package/dist/_internal/server/features/products/list-public.js +95 -7
  19. package/dist/_internal/server/features/site-settings/adapters.d.ts +2 -2
  20. package/dist/_internal/server/features/site-settings/adapters.js +8 -5
  21. package/dist/_internal/server/features/tester/visibility.d.ts +2 -1
  22. package/dist/_internal/server/features/tester/visibility.js +2 -2
  23. package/dist/_internal/server/functions/scheduled.d.ts +3 -1
  24. package/dist/_internal/server/functions/scheduled.js +27 -1
  25. package/dist/_internal/server/jobs/core/hardBanCascade.js +4 -0
  26. package/dist/_internal/server/jobs/core/index.d.ts +2 -0
  27. package/dist/_internal/server/jobs/core/index.js +2 -0
  28. package/dist/_internal/server/jobs/core/pageViewPrune.d.ts +10 -0
  29. package/dist/_internal/server/jobs/core/pageViewPrune.js +44 -0
  30. package/dist/_internal/server/jobs/core/pageViewRollup.d.ts +2 -0
  31. package/dist/_internal/server/jobs/core/pageViewRollup.js +87 -0
  32. package/dist/_internal/server/jobs/handlers/index.d.ts +2 -0
  33. package/dist/_internal/server/jobs/handlers/index.js +2 -0
  34. package/dist/_internal/server/jobs/handlers/pageViewPrune.d.ts +2 -0
  35. package/dist/_internal/server/jobs/handlers/pageViewPrune.js +2 -0
  36. package/dist/_internal/server/jobs/handlers/pageViewRollup.d.ts +2 -0
  37. package/dist/_internal/server/jobs/handlers/pageViewRollup.js +2 -0
  38. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  39. package/dist/_internal/shared/actions/action-registry.js +69 -22
  40. package/dist/_internal/shared/checkout/rules/classified.rule.d.ts +9 -4
  41. package/dist/_internal/shared/checkout/rules/classified.rule.js +7 -2
  42. package/dist/_internal/shared/features/layout/matchesNavQuery.d.ts +14 -0
  43. package/dist/_internal/shared/features/layout/matchesNavQuery.js +72 -0
  44. package/dist/_internal/shared/features/layout/types.d.ts +37 -1
  45. package/dist/_internal/shared/features/layout/types.js +19 -1
  46. package/dist/_internal/shared/features/notifications/action-url.d.ts +9 -1
  47. package/dist/_internal/shared/features/notifications/action-url.js +47 -0
  48. package/dist/_internal/shared/features/offers/config.d.ts +65 -0
  49. package/dist/_internal/shared/features/offers/config.js +73 -2
  50. package/dist/_internal/shared/features/orders/schema.d.ts +19 -1
  51. package/dist/_internal/shared/features/orders/schema.js +22 -2
  52. package/dist/_internal/shared/features/site-settings/types.d.ts +12 -3
  53. package/dist/_internal/shared/listing-types/capabilities.d.ts +8 -2
  54. package/dist/_internal/shared/listing-types/capabilities.js +6 -1
  55. package/dist/_internal/shared/listing-types/classified/ctas.js +6 -1
  56. package/dist/_internal/shared/listing-types/feature-flags.d.ts +10 -5
  57. package/dist/_internal/shared/listing-types/feature-flags.js +11 -6
  58. package/dist/client.d.ts +37 -20
  59. package/dist/client.js +36 -14
  60. package/dist/configs/next.js +10 -2
  61. package/dist/constants/api-endpoints.d.ts +18 -54
  62. package/dist/constants/api-endpoints.js +6 -26
  63. package/dist/constants/field-names.d.ts +6 -24
  64. package/dist/constants/field-names.js +10 -27
  65. package/dist/constants/geo/countries.d.ts +50 -0
  66. package/dist/constants/geo/countries.js +129 -0
  67. package/dist/constants/geo/subdivisions.d.ts +13 -0
  68. package/dist/constants/geo/subdivisions.js +104 -0
  69. package/dist/constants/index.d.ts +2 -2
  70. package/dist/constants/index.js +2 -2
  71. package/dist/constants/table-keys.d.ts +24 -0
  72. package/dist/constants/table-keys.js +28 -0
  73. package/dist/contracts/extend.d.ts +0 -9
  74. package/dist/contracts/index.d.ts +1 -1
  75. package/dist/contracts/realtime-session.js +7 -0
  76. package/dist/core/browser.d.ts +1 -1
  77. package/dist/core/browser.js +1 -1
  78. package/dist/core/index.d.ts +1 -1
  79. package/dist/core/index.js +1 -1
  80. package/dist/core/site-config.d.ts +0 -3
  81. package/dist/core/site-config.js +0 -3
  82. package/dist/errors/messages.d.ts +0 -8
  83. package/dist/errors/messages.js +0 -9
  84. package/dist/features/about/schemas/index.d.ts +8 -8
  85. package/dist/features/account/components/AddressFilters.js +32 -11
  86. package/dist/features/account/components/AddressForm.d.ts +26 -25
  87. package/dist/features/account/components/AddressForm.js +81 -103
  88. package/dist/features/account/components/AddressesIndexListing.js +8 -4
  89. package/dist/features/account/components/UserSidebar.d.ts +3 -4
  90. package/dist/features/account/components/UserSupportView.js +89 -58
  91. package/dist/features/account/components/index.d.ts +1 -7
  92. package/dist/features/account/components/index.js +0 -3
  93. package/dist/features/account/hooks/useAddresses.d.ts +4 -3
  94. package/dist/features/account/hooks/useAddresses.js +4 -6
  95. package/dist/features/account/hooks/useCollapsedSections.d.ts +51 -10
  96. package/dist/features/account/hooks/useCollapsedSections.js +211 -38
  97. package/dist/features/account/hooks/useProfile.d.ts +8 -0
  98. package/dist/features/account/index.d.ts +0 -3
  99. package/dist/features/account/index.js +0 -3
  100. package/dist/features/account/schemas/index.d.ts +0 -92
  101. package/dist/features/account/schemas/index.js +42 -20
  102. package/dist/features/addresses/components/AddressLocationFields.d.ts +13 -0
  103. package/dist/features/addresses/components/AddressLocationFields.js +41 -0
  104. package/dist/features/addresses/hooks/usePostalLookup.d.ts +17 -0
  105. package/dist/features/addresses/hooks/usePostalLookup.js +133 -0
  106. package/dist/features/addresses/schemas/address-form.d.ts +423 -0
  107. package/dist/features/addresses/schemas/address-form.js +212 -0
  108. package/dist/features/addresses/schemas/index.d.ts +1 -0
  109. package/dist/features/addresses/schemas/index.js +8 -0
  110. package/dist/features/admin/actions/admin-read-actions.d.ts +1 -1
  111. package/dist/features/admin/actions/index.d.ts +0 -1
  112. package/dist/features/admin/actions/index.js +0 -1
  113. package/dist/features/admin/actions/notification-actions.js +1 -41
  114. package/dist/features/admin/components/AdminActionIndexView.d.ts +32 -0
  115. package/dist/features/admin/components/AdminActionIndexView.js +128 -0
  116. package/dist/features/admin/components/AdminAdEditorView.js +159 -163
  117. package/dist/features/admin/components/AdminAddressEditorView.js +97 -55
  118. package/dist/features/admin/components/AdminBrandEditorView.d.ts +15 -0
  119. package/dist/features/admin/components/AdminBrandEditorView.js +113 -77
  120. package/dist/features/admin/components/AdminBundleEditorView.d.ts +19 -9
  121. package/dist/features/admin/components/AdminBundleEditorView.js +136 -168
  122. package/dist/features/admin/components/AdminCategoryEditorView.js +120 -43
  123. package/dist/features/admin/components/AdminCouponEditorView.js +145 -109
  124. package/dist/features/admin/components/AdminEmployeeEditorView.js +125 -56
  125. package/dist/features/admin/components/AdminFaqEditorView.js +112 -58
  126. package/dist/features/admin/components/AdminFeatureEditorView.js +108 -68
  127. package/dist/features/admin/components/AdminNavEditorView.js +69 -44
  128. package/dist/features/admin/components/AdminOrderEditorView.js +47 -10
  129. package/dist/features/admin/components/AdminPrizeDrawsView.js +16 -2
  130. package/dist/features/admin/components/AdminSidebar.d.ts +11 -7
  131. package/dist/features/admin/components/AdminSiteSettingsView.js +37 -37
  132. package/dist/features/admin/components/AdminStoreEditorView.js +75 -52
  133. package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +71 -30
  134. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +95 -65
  135. package/dist/features/admin/components/BrandQuickCreateForm.d.ts +10 -0
  136. package/dist/features/admin/components/BrandQuickCreateForm.js +49 -26
  137. package/dist/features/admin/components/CategoryQuickCreateForm.d.ts +11 -0
  138. package/dist/features/admin/components/CategoryQuickCreateForm.js +48 -26
  139. package/dist/features/admin/components/index.d.ts +2 -2
  140. package/dist/features/admin/components/index.js +1 -1
  141. package/dist/features/admin/constants/site-settings-tabs.d.ts +120 -0
  142. package/dist/features/admin/constants/site-settings-tabs.js +152 -0
  143. package/dist/features/admin/index.d.ts +0 -3
  144. package/dist/features/admin/index.js +0 -2
  145. package/dist/features/admin/repository/analytics-rollup.repository.d.ts +35 -0
  146. package/dist/features/admin/repository/analytics-rollup.repository.js +30 -0
  147. package/dist/features/admin/schemas/admin-editor-forms.d.ts +56 -57
  148. package/dist/features/admin/schemas/admin-editor-forms.js +97 -54
  149. package/dist/features/admin/schemas/admin-ops-forms.d.ts +57 -5
  150. package/dist/features/admin/schemas/admin-ops-forms.js +104 -20
  151. package/dist/features/admin/schemas/admin-user-form.js +7 -0
  152. package/dist/features/admin/schemas/firestore.d.ts +48 -95
  153. package/dist/features/admin/schemas/firestore.js +15 -151
  154. package/dist/features/admin/schemas/nav-item-form.js +9 -2
  155. package/dist/features/admin/server.d.ts +0 -4
  156. package/dist/features/admin/server.js +0 -2
  157. package/dist/features/admin/types/index.d.ts +0 -7
  158. package/dist/features/analytics/repository/page-views.repository.d.ts +14 -0
  159. package/dist/features/analytics/repository/page-views.repository.js +24 -0
  160. package/dist/features/auth/actions/index.d.ts +0 -1
  161. package/dist/features/auth/actions/index.js +0 -1
  162. package/dist/features/auth/actions/realtime-token-actions.js +1 -12
  163. package/dist/features/auth/auth-helpers.js +5 -7
  164. package/dist/features/auth/components/Guards.js +2 -1
  165. package/dist/features/auth/components/LoginForm.d.ts +0 -2
  166. package/dist/features/auth/components/LoginForm.js +1 -3
  167. package/dist/features/auth/components/RegisterForm.js +62 -19
  168. package/dist/features/auth/permissions/constants.d.ts +27 -1
  169. package/dist/features/auth/role-predicates.d.ts +75 -7
  170. package/dist/features/auth/role-predicates.js +28 -2
  171. package/dist/features/auth/schemas/firestore.d.ts +22 -0
  172. package/dist/features/auth/schemas/index.d.ts +1 -1
  173. package/dist/features/auth/schemas/index.js +44 -8
  174. package/dist/features/auth/types/index.d.ts +14 -1
  175. package/dist/features/blog/api/route.js +10 -2
  176. package/dist/features/blog/components/BlogIndexListing.js +1 -1
  177. package/dist/features/blog/hooks/useBlog.js +2 -2
  178. package/dist/features/blog/schemas/index.d.ts +4 -0
  179. package/dist/features/blog/schemas/index.js +2 -0
  180. package/dist/features/blog/types/index.d.ts +1 -1
  181. package/dist/features/catalogue/components/CatalogueItemEditorView.d.ts +22 -0
  182. package/dist/features/catalogue/components/CatalogueItemEditorView.js +112 -52
  183. package/dist/features/catalogue/schemas/validation.d.ts +4 -4
  184. package/dist/features/catalogue/schemas/validation.js +47 -10
  185. package/dist/features/categories/schemas/bundle-form.d.ts +142 -0
  186. package/dist/features/categories/schemas/bundle-form.js +149 -0
  187. package/dist/features/classified/components/ClassifiedDetailPageView.d.ts +5 -4
  188. package/dist/features/classified/components/ClassifiedDetailPageView.js +37 -16
  189. package/dist/features/consultation/components/ConsultationForm.js +1 -1
  190. package/dist/features/consultation/schemas/index.js +34 -10
  191. package/dist/features/corporate/schemas/index.js +36 -15
  192. package/dist/features/filters/FilterPanel.js +3 -3
  193. package/dist/features/filters/RangeFilter.d.ts +10 -0
  194. package/dist/features/filters/RangeFilter.js +41 -4
  195. package/dist/features/homepage/components/MarketplaceHomepageView.js +24 -5
  196. package/dist/features/homepage/hooks/useBlogArticles.js +1 -1
  197. package/dist/features/layout/BottomActions.js +84 -11
  198. package/dist/features/layout/BottomActionsContext.d.ts +1 -6
  199. package/dist/features/layout/BottomActionsContext.js +1 -1
  200. package/dist/features/layout/hooks/useBottomActions.js +2 -2
  201. package/dist/features/layout/hooks/useFormBottomActions.d.ts +12 -1
  202. package/dist/features/layout/hooks/useFormBottomActions.js +12 -6
  203. package/dist/features/lottery/schemas/config-write.d.ts +9 -5
  204. package/dist/features/lottery/schemas/config-write.js +69 -9
  205. package/dist/features/orders/schemas/buyer-forms.d.ts +6 -3
  206. package/dist/features/orders/schemas/buyer-forms.js +31 -3
  207. package/dist/features/orders/schemas/index.d.ts +2 -2
  208. package/dist/features/products/components/GroupSettingsPanel.js +3 -1
  209. package/dist/features/products/components/MakeOfferButton.d.ts +13 -2
  210. package/dist/features/products/components/MakeOfferButton.js +33 -11
  211. package/dist/features/products/components/ProductDetailActions.js +0 -1
  212. package/dist/features/products/components/ProductDetailPageView.d.ts +7 -3
  213. package/dist/features/products/components/ProductDetailPageView.js +8 -5
  214. package/dist/features/products/constants/action-defs.d.ts +1 -2
  215. package/dist/features/products/constants/action-defs.js +40 -43
  216. package/dist/features/products/repository/products.repository.d.ts +5 -1
  217. package/dist/features/products/repository/products.repository.js +23 -1
  218. package/dist/features/products/schemas/firestore.d.ts +7 -3
  219. package/dist/features/products/schemas/product-features.validators.js +3 -0
  220. package/dist/features/promotions/components/CouponCard.js +8 -7
  221. package/dist/features/scams/schemas/firestore.d.ts +15 -1
  222. package/dist/features/scams/schemas/report-form.d.ts +5 -5
  223. package/dist/features/scams/schemas/report-form.js +18 -3
  224. package/dist/features/search/action-index/derive.d.ts +49 -0
  225. package/dist/features/search/action-index/derive.js +121 -0
  226. package/dist/features/search/action-index/repository.d.ts +42 -0
  227. package/dist/features/search/action-index/repository.js +100 -0
  228. package/dist/features/search/action-index/settings-entries.d.ts +41 -0
  229. package/dist/features/search/action-index/settings-entries.js +268 -0
  230. package/dist/features/search/action-index/types.d.ts +81 -0
  231. package/dist/features/search/action-index/types.js +102 -0
  232. package/dist/features/search/action-index/validate-entry.d.ts +34 -0
  233. package/dist/features/search/action-index/validate-entry.js +114 -0
  234. package/dist/features/search/hooks/useNavSuggestions.d.ts +5 -1
  235. package/dist/features/search/hooks/useNavSuggestions.js +23 -3
  236. package/dist/features/search/repository/search.repository.js +3 -1
  237. package/dist/features/seller/actions/offer-actions.js +43 -24
  238. package/dist/features/seller/actions/seller-actions.js +29 -5
  239. package/dist/features/seller/components/CategoryChipPicker.d.ts +9 -0
  240. package/dist/features/seller/components/CategoryChipPicker.js +12 -0
  241. package/dist/features/seller/components/SellerAddressesView.js +5 -1
  242. package/dist/features/seller/components/SellerAnalyticsAlertsView.js +83 -43
  243. package/dist/features/seller/components/SellerArtView.d.ts +14 -7
  244. package/dist/features/seller/components/SellerArtView.js +4 -119
  245. package/dist/features/seller/components/SellerClassifiedView.d.ts +14 -7
  246. package/dist/features/seller/components/SellerClassifiedView.js +4 -124
  247. package/dist/features/seller/components/SellerCouponEditorView.d.ts +22 -1
  248. package/dist/features/seller/components/SellerCouponEditorView.js +91 -45
  249. package/dist/features/seller/components/SellerDigitalCodesView.d.ts +14 -7
  250. package/dist/features/seller/components/SellerDigitalCodesView.js +4 -126
  251. package/dist/features/seller/components/SellerListingTypeView.d.ts +66 -0
  252. package/dist/features/seller/components/SellerListingTypeView.js +277 -0
  253. package/dist/features/seller/components/SellerLiveView.d.ts +14 -7
  254. package/dist/features/seller/components/SellerLiveView.js +4 -126
  255. package/dist/features/seller/components/SellerSidebar.d.ts +3 -6
  256. package/dist/features/seller/components/SellerStickersView.d.ts +14 -7
  257. package/dist/features/seller/components/SellerStickersView.js +4 -119
  258. package/dist/features/seller/components/SellerSupportView.d.ts +31 -0
  259. package/dist/features/seller/components/SellerSupportView.js +100 -0
  260. package/dist/features/seller/components/index.d.ts +4 -8
  261. package/dist/features/seller/components/index.js +2 -4
  262. package/dist/features/seller/index.d.ts +0 -1
  263. package/dist/features/seller/index.js +0 -1
  264. package/dist/features/seller/schemas/coupon-form.d.ts +214 -0
  265. package/dist/features/seller/schemas/coupon-form.js +155 -23
  266. package/dist/features/seller/schemas/offer-forms.d.ts +16 -7
  267. package/dist/features/seller/schemas/offer-forms.js +28 -12
  268. package/dist/features/shell/FormShell.js +2 -2
  269. package/dist/features/shell/QuickFormDrawer.js +2 -2
  270. package/dist/features/shell/SectionForm.d.ts +19 -1
  271. package/dist/features/shell/SectionForm.js +104 -17
  272. package/dist/features/shell/build-sections.d.ts +23 -0
  273. package/dist/features/shell/build-sections.js +106 -8
  274. package/dist/features/shell/field-ui-meta.d.ts +44 -0
  275. package/dist/features/shell/field-ui-meta.js +14 -7
  276. package/dist/features/shell/form-sections.d.ts +46 -0
  277. package/dist/features/shell/form-sections.js +114 -0
  278. package/dist/features/shipments/components/AdminShipmentEditorView.js +87 -64
  279. package/dist/features/shipments/schemas/validation.d.ts +2 -2
  280. package/dist/features/shipments/schemas/validation.js +44 -13
  281. package/dist/features/store-extensions/schemas/custom-role-form.js +6 -2
  282. package/dist/features/store-extensions/schemas/listing-template-form.d.ts +7 -7
  283. package/dist/features/store-extensions/schemas/payout-method-form.js +14 -0
  284. package/dist/features/stores/actions/store-query-actions.js +3 -2
  285. package/dist/features/stores/api/[storeSlug]/auctions/route.js +3 -1
  286. package/dist/features/stores/api/[storeSlug]/products/route.js +3 -1
  287. package/dist/features/support/repository/support.repository.d.ts +21 -0
  288. package/dist/features/support/repository/support.repository.js +63 -0
  289. package/dist/features/support/schemas/firestore.d.ts +27 -2
  290. package/dist/features/support/schemas/firestore.js +2 -0
  291. package/dist/features/support/schemas/ticket-create-form.js +7 -0
  292. package/dist/features/tester/schemas/checklist-item-form.d.ts +43 -0
  293. package/dist/features/tester/schemas/checklist-item-form.js +109 -0
  294. package/dist/features/tester/seed-data/blog-tester-seed-data.js +2 -2
  295. package/dist/features/tester/seed-data/categories-tester-seed-data.js +2 -2
  296. package/dist/features/tester/seed-data/events-tester-seed-data.js +5 -5
  297. package/dist/features/tester/seed-data/orders-tester-seed-data.js +2 -2
  298. package/dist/features/tester/seed-data/products-tester-seed-data.js +48 -49
  299. package/dist/features/tester/seed-data/stores-tester-seed-data.js +3 -3
  300. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +275 -29
  301. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +48 -12
  302. package/dist/http/api-handler.js +2 -1
  303. package/dist/index.d.ts +24 -103
  304. package/dist/index.js +33 -141
  305. package/dist/jobs.d.ts +1 -1
  306. package/dist/jobs.js +1 -1
  307. package/dist/monitoring/browser.d.ts +0 -2
  308. package/dist/monitoring/browser.js +12 -1
  309. package/dist/monitoring/index.d.ts +0 -2
  310. package/dist/monitoring/index.js +0 -1
  311. package/dist/next/api/routeHandler.js +6 -3
  312. package/dist/next/index.d.ts +1 -2
  313. package/dist/next/index.js +0 -1
  314. package/dist/next/middleware/index.d.ts +0 -2
  315. package/dist/next/middleware/index.js +0 -1
  316. package/dist/next/routing/route-map.d.ts +8 -7
  317. package/dist/next/routing/route-map.js +10 -3
  318. package/dist/providers/db-firebase/admin.js +15 -1
  319. package/dist/providers/db-firebase/sieve.d.ts +22 -0
  320. package/dist/react/hooks/useListingTypeFlags.d.ts +1 -1
  321. package/dist/react/hooks/useListingTypeFlags.js +1 -1
  322. package/dist/react/hooks/usePendingFilters.js +5 -3
  323. package/dist/react/hooks/usePendingTable.js +15 -3
  324. package/dist/repositories/index.d.ts +0 -1
  325. package/dist/repositories/index.js +0 -1
  326. package/dist/schemas/registry.d.ts +197 -232
  327. package/dist/schemas/registry.js +5 -6
  328. package/dist/schemas/webhooks/razorpay.d.ts +50 -50
  329. package/dist/security/authorization.d.ts +9 -0
  330. package/dist/security/authorization.js +16 -2
  331. package/dist/security/browser.d.ts +1 -3
  332. package/dist/security/browser.js +5 -2
  333. package/dist/security/index.d.ts +1 -3
  334. package/dist/security/index.js +19 -2
  335. package/dist/security/pii-encrypt.js +16 -0
  336. package/dist/security/pii-schemas.d.ts +0 -14
  337. package/dist/security/pii-schemas.js +7 -15
  338. package/dist/seed/_helpers/category-forest.js +2 -2
  339. package/dist/seed/_helpers/media.d.ts +19 -0
  340. package/dist/seed/_helpers/media.js +75 -0
  341. package/dist/seed/blog-posts-seed-data.js +17 -17
  342. package/dist/seed/carousel-slides-seed-data.js +7 -7
  343. package/dist/seed/cart-seed-data.js +13 -13
  344. package/dist/seed/categories-seed-data.js +10 -10
  345. package/dist/seed/events-seed-data.js +24 -24
  346. package/dist/seed/factories/product.factory.js +4 -4
  347. package/dist/seed/factories/store.factory.js +3 -3
  348. package/dist/seed/factories/user.factory.js +2 -2
  349. package/dist/seed/grouped-listings-seed-data.js +11 -11
  350. package/dist/seed/homepage-sections-seed-data.js +2 -2
  351. package/dist/seed/index.d.ts +0 -1
  352. package/dist/seed/index.js +0 -1
  353. package/dist/seed/manifest.d.ts +1 -1
  354. package/dist/seed/manifest.js +0 -5
  355. package/dist/seed/notifications-seed-data.js +20 -1
  356. package/dist/seed/orders-seed-data.js +2 -2
  357. package/dist/seed/products-art-seed-data.js +16 -16
  358. package/dist/seed/products-auctions-seed-data.js +34 -34
  359. package/dist/seed/products-classifieds-seed-data.js +35 -37
  360. package/dist/seed/products-digital-codes-seed-data.js +20 -20
  361. package/dist/seed/products-live-items-seed-data.js +11 -11
  362. package/dist/seed/products-preorders-seed-data.js +13 -13
  363. package/dist/seed/products-prize-draws-seed-data.js +43 -43
  364. package/dist/seed/products-standard-seed-data.js +54 -54
  365. package/dist/seed/products-stickers-seed-data.js +15 -15
  366. package/dist/seed/reviews-seed-data.js +4 -4
  367. package/dist/seed/scammers-seed-data.js +3 -3
  368. package/dist/seed/site-settings-seed-data.js +11 -24
  369. package/dist/seed/store-extensions-seed-data.js +10 -10
  370. package/dist/seed/stores-seed-data.js +11 -11
  371. package/dist/seed/support-tickets-seed-data.d.ts +14 -0
  372. package/dist/seed/support-tickets-seed-data.js +20 -1
  373. package/dist/seed/users-seed-data.js +20 -20
  374. package/dist/server-entry.d.ts +0 -1
  375. package/dist/server-entry.js +0 -1
  376. package/dist/server.d.ts +9 -6
  377. package/dist/server.js +7 -13
  378. package/dist/styles.css +248 -68
  379. package/dist/tailwind-utilities.css +1 -1
  380. package/dist/tokens/themes/default-dark.js +2 -2
  381. package/dist/tokens/themes/default-light.js +2 -2
  382. package/dist/tokens/tokens.css +24 -4
  383. package/dist/ui/components/ActionRow.d.ts +50 -0
  384. package/dist/ui/components/ActionRow.js +53 -0
  385. package/dist/ui/components/ActionRow.style.css +145 -0
  386. package/dist/ui/components/Drawer.d.ts +10 -0
  387. package/dist/ui/components/Drawer.js +2 -1
  388. package/dist/ui/components/FilterDrawer.js +4 -1
  389. package/dist/ui/components/FilterDrawer.style.css +6 -12
  390. package/dist/ui/components/FormActionBar.js +14 -1
  391. package/dist/ui/components/FormActionBar.style.css +12 -11
  392. package/dist/ui/components/ListingLayout.js +8 -1
  393. package/dist/ui/components/ListingLayout.style.css +6 -13
  394. package/dist/ui/components/Modal.d.ts +15 -1
  395. package/dist/ui/components/Modal.js +17 -3
  396. package/dist/ui/components/QuickCreateModal.js +2 -2
  397. package/dist/ui/components/QuickCreateModal.style.css +78 -84
  398. package/dist/ui/components/RecordDetailModal.js +2 -2
  399. package/dist/ui/components/SideDrawer.d.ts +8 -0
  400. package/dist/ui/components/SideDrawer.js +2 -1
  401. package/dist/ui/components/SideDrawer.style.css +6 -3
  402. package/dist/ui/components/SideModal.d.ts +11 -1
  403. package/dist/ui/components/SideModal.js +3 -2
  404. package/dist/ui/components/SideModal.style.css +14 -0
  405. package/dist/ui/components/Typography.style.css +33 -24
  406. package/dist/ui/components/index.style.css +1 -0
  407. package/dist/ui/forms/FormShell.style.css +260 -311
  408. package/dist/ui/index.d.ts +2 -0
  409. package/dist/ui/index.js +1 -0
  410. package/dist/utils/index.d.ts +0 -1
  411. package/dist/utils/index.js +0 -1
  412. package/dist/validation/index.d.ts +1 -1
  413. package/dist/validation/index.js +1 -1
  414. package/dist/validation/schemas.d.ts +0 -19
  415. package/dist/validation/schemas.js +8 -19
  416. package/package.json +1 -1
  417. package/scripts/seed-cli.mjs +3 -6
@@ -7,7 +7,7 @@ import { isChannelHealthy, withChannelRetry } from "../../../_internal/server/no
7
7
  import { enqueueJob } from "../../jobs/actions/enqueue-job";
8
8
  import { serverLogger } from "../../../monitoring";
9
9
  import { decryptPii } from "../../../security/index";
10
- import { resolveNotificationActionUrl, } from "../../../_internal/shared/features/notifications/action-url";
10
+ import { resolveNotificationActionUrl, TYPE_AUDIENCE, } from "../../../_internal/shared/features/notifications/action-url";
11
11
  import { renderNotificationEmail } from "../../email/notification-templates";
12
12
  /**
13
13
  * A notification's `actionUrl` is a site-relative path — correct for the
@@ -100,46 +100,6 @@ export async function listNotifications(userId, limit = 20) {
100
100
  export async function getUnreadNotificationCount(userId) {
101
101
  return notificationRepository.getUnreadCount(userId);
102
102
  }
103
- /**
104
- * Which portal each notification type belongs to.
105
- *
106
- * `Record<NotificationType, …>`, so a new type cannot compile without an
107
- * answer — the same discipline `typeToPrefsKey` needed after it silently
108
- * omitted three types and ignored those users' opt-outs.
109
- */
110
- const TYPE_AUDIENCE = {
111
- // Buyer-facing: the order lifecycle they are waiting on.
112
- order_placed: "buyer",
113
- order_confirmed: "buyer",
114
- order_shipped: "buyer",
115
- order_delivered: "buyer",
116
- order_cancelled: "buyer",
117
- refund_initiated: "buyer",
118
- payment_review: "buyer",
119
- emi_installment_due_soon: "buyer",
120
- emi_installment_overdue: "buyer",
121
- prize_won: "buyer",
122
- prize_reveal_expired: "buyer",
123
- product_available: "buyer",
124
- bid_placed: "buyer",
125
- bid_outbid: "buyer",
126
- bid_won: "buyer",
127
- bid_lost: "buyer",
128
- offer_responded: "buyer",
129
- offer_counter_accepted: "buyer",
130
- offer_expired: "buyer",
131
- review_replied: "buyer",
132
- catalogue_images_stale: "buyer",
133
- // Seller-facing.
134
- auction_ended: "seller", // "your auction closed with no winner"
135
- offer_received: "seller",
136
- review_approved: "seller",
137
- // Account-level, which lives in the user portal for everyone.
138
- welcome: "buyer",
139
- account_action: "buyer",
140
- system: "buyer",
141
- promotion: "buyer",
142
- };
143
103
  /**
144
104
  * Create an in-app notification and fan out to enabled external channels.
145
105
  *
@@ -0,0 +1,32 @@
1
+ /**
2
+ * AdminActionIndexView — the one control plane for "what can a user do here,
3
+ * and where is it" (D7).
4
+ *
5
+ * ## It absorbs two editors that reached nothing
6
+ *
7
+ * `/admin/navigation` (CRUD over a Firestore collection) and
8
+ * `/admin/settings/navigation` (toggles over `siteSettings.navConfig`, seeded
9
+ * from an 11-item hardcoded array) both existed, and **neither one's data
10
+ * reached any sidebar** — because `filterNavItems` short-circuits on a missing
11
+ * `id` and no nav item had one until W6. Two admin screens editing settings
12
+ * nothing read.
13
+ *
14
+ * This screen lists every entry with its kind, portal, route, deep link and
15
+ * required permission, and the toggle here does reach the sidebar.
16
+ *
17
+ * ## What an admin may change, and what they may not
18
+ *
19
+ * Label, description, keywords, weight and enabled — content. Not `href`, not
20
+ * `kind`, not any permission field: repointing a built-in route is routing,
21
+ * and relaxing a permission is access control. An admin-authored entry brings
22
+ * its own href and is validated at SAVE time, because the audit runs on CI and
23
+ * a runtime-written bad link would 404 until the next run.
24
+ */
25
+ import React from "react";
26
+ import type { StackedViewShellProps } from "../../../ui";
27
+ export interface AdminActionIndexViewProps extends Omit<StackedViewShellProps, "sections"> {
28
+ labels?: {
29
+ title?: string;
30
+ };
31
+ }
32
+ export declare function AdminActionIndexView({ labels }: AdminActionIndexViewProps): React.JSX.Element;
@@ -0,0 +1,128 @@
1
+ "use client";
2
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
3
+ /**
4
+ * AdminActionIndexView — the one control plane for "what can a user do here,
5
+ * and where is it" (D7).
6
+ *
7
+ * ## It absorbs two editors that reached nothing
8
+ *
9
+ * `/admin/navigation` (CRUD over a Firestore collection) and
10
+ * `/admin/settings/navigation` (toggles over `siteSettings.navConfig`, seeded
11
+ * from an 11-item hardcoded array) both existed, and **neither one's data
12
+ * reached any sidebar** — because `filterNavItems` short-circuits on a missing
13
+ * `id` and no nav item had one until W6. Two admin screens editing settings
14
+ * nothing read.
15
+ *
16
+ * This screen lists every entry with its kind, portal, route, deep link and
17
+ * required permission, and the toggle here does reach the sidebar.
18
+ *
19
+ * ## What an admin may change, and what they may not
20
+ *
21
+ * Label, description, keywords, weight and enabled — content. Not `href`, not
22
+ * `kind`, not any permission field: repointing a built-in route is routing,
23
+ * and relaxing a permission is access control. An admin-authored entry brings
24
+ * its own href and is validated at SAVE time, because the audit runs on CI and
25
+ * a runtime-written bad link would 404 until the next run.
26
+ */
27
+ import React from "react";
28
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
29
+ import { useApiMutation } from "@mohasinac/appkit/client";
30
+ import { Alert, Badge, Row, Span, Stack, Text, Toggle, useToast } from "../../../ui";
31
+ import { apiClient } from "../../../http";
32
+ import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
33
+ import { DataListingView } from "./DataListingView";
34
+ const ENDPOINT = ADMIN_ENDPOINTS.ACTION_INDEX;
35
+ export function AdminActionIndexView({ labels = {} }) {
36
+ const queryClient = useQueryClient();
37
+ const { showToast } = useToast();
38
+ const query = useQuery({
39
+ queryKey: ["admin", "action-index"],
40
+ queryFn: () => apiClient.get(ENDPOINT),
41
+ staleTime: 60000,
42
+ });
43
+ const payload = query.data?.data;
44
+ const overrides = payload?.control?.entries ?? {};
45
+ const toggleMutation = useApiMutation({
46
+ errorMessage: "Could not update that entry.",
47
+ mutationFn: async (vars) => apiClient.patch(ENDPOINT, {
48
+ entryId: vars.entryId,
49
+ override: { ...overrides[vars.entryId], enabled: vars.enabled },
50
+ }),
51
+ onSuccess: () => {
52
+ showToast("Saved.", "success");
53
+ void queryClient.invalidateQueries({ queryKey: ["admin", "action-index"] });
54
+ },
55
+ });
56
+ /*
57
+ * Ordered by weight, then label — the same ranking `matchesNavQuery` gives
58
+ * the search, so the admin reading this list sees entries in the order a
59
+ * user would meet them.
60
+ */
61
+ const rows = React.useMemo(() => [...(payload?.entries ?? [])]
62
+ .sort((a, b) => (b.weight ?? 0) - (a.weight ?? 0) || a.label.localeCompare(b.label))
63
+ .map((entry) => ({
64
+ id: entry.id,
65
+ primary: entry.label,
66
+ secondary: entry.description ?? entry.sectionPath ?? "",
67
+ kind: entry.kind,
68
+ portal: entry.portal,
69
+ href: `${entry.href}${entry.deepLink ?? ""}`,
70
+ permission: entry.requiredPermission ?? "—",
71
+ enabled: overrides[entry.id]?.enabled !== false,
72
+ })), [payload?.entries, overrides]);
73
+ const sync = payload?.sync;
74
+ return (_jsxs(Stack, { gap: "md", children: [sync && sync.orphanedOverrides.length > 0 && (_jsxs(Alert, { variant: "warning", title: "Overrides with no entry", children: [sync.orphanedOverrides.length, " saved override", sync.orphanedOverrides.length === 1 ? "" : "s", " point at entries the code no longer has (", sync.orphanedOverrides.slice(0, 4).join(", "), sync.orphanedOverrides.length > 4 ? "…" : "", "). They are ignored \u2014 clear them when convenient."] })), _jsx(DataListingView, { config: {
75
+ portal: "admin",
76
+ title: labels.title ?? "Action index",
77
+ subtitle: "Every page, setting and toggle a user can reach — and what the search finds them by.",
78
+ search: {
79
+ placeholder: "Search by label, description or keyword…",
80
+ mode: "partial",
81
+ fields: ["label", "description", "keywords"],
82
+ commit: "debounce",
83
+ },
84
+ emptyLabel: "No entries match.",
85
+ queryKey: ["admin", "action-index", "listing"],
86
+ endpoint: ENDPOINT,
87
+ filterKeys: ["portal", "kind"],
88
+ defaultSort: "",
89
+ /*
90
+ * 🛑 No sort dropdown, on purpose.
91
+ *
92
+ * This endpoint returns ONE document — the whole index, ~380 entries
93
+ * — and does not sort. Offering a dropdown here would be Root Cause
94
+ * #63 exactly: an option whose field the query never applies, so the
95
+ * control reorders nothing and says nothing about it. The rows are
96
+ * ordered by weight then label below, which is the ranking the
97
+ * search itself uses.
98
+ */
99
+ sortOptions: [],
100
+ mapRows: () => rows,
101
+ getTotal: () => rows.length,
102
+ buildFilters: () => undefined,
103
+ columns: [
104
+ { key: "primary", header: "Entry" },
105
+ {
106
+ key: "kind",
107
+ header: "Kind",
108
+ render: (row) => _jsx(Badge, { variant: "default", children: row.kind }),
109
+ },
110
+ { key: "portal", header: "Portal" },
111
+ {
112
+ key: "href",
113
+ header: "Goes to",
114
+ render: (row) => (_jsx(Span, { size: "xs", family: "mono", color: "muted", children: row.href })),
115
+ },
116
+ {
117
+ key: "permission",
118
+ header: "Needs",
119
+ render: (row) => (_jsx(Span, { size: "xs", color: "muted", children: row.permission })),
120
+ },
121
+ {
122
+ key: "enabled",
123
+ header: "Shown",
124
+ render: (row) => (_jsx(Row, { align: "center", gap: "sm", children: _jsx(Toggle, { checked: row.enabled, size: "sm", "aria-label": `Show ${row.primary}`, onChange: (next) => toggleMutation.mutate({ entryId: row.id, enabled: next }) }) })),
125
+ },
126
+ ],
127
+ } }), _jsx(Text, { size: "xs", color: "muted", children: "Hiding an entry removes it from the sidebar, the header search and the command palette at once. Labels, descriptions and keywords can be edited here; a route and its required permission belong to the code." })] }));
128
+ }
@@ -1,55 +1,60 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { normalizeError } from "../../../errors/normalize";
4
3
  import { useApiMutation } from "@mohasinac/appkit/client";
5
4
  import React from "react";
6
5
  import { useQuery } from "@tanstack/react-query";
7
- import { Alert, Button, Checkbox, Div, Form, FormActions, Input, Select, Span, Stack, StackedViewShell, Text, Toggle, useToast } from "../../../ui";
6
+ import { Alert, Checkbox, Div, Span, Stack, StackedViewShell, Text, useToast } from "../../../ui";
8
7
  import { apiClient } from "../../../http";
9
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
9
  import { adminAdFormSchema } from "../schemas/admin-ops-forms";
11
10
  import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
11
+ import { FormShellContext, useFormShellState } from "../../../ui/forms/FormShell";
12
+ import { applyZodIssues } from "../../../ui/forms/apply-zod-issues";
13
+ import { buildSectionsFromSchema, visibleValues } from "../../shell/build-sections";
14
+ import { SectionForm, useSectionFormNav } from "../../shell/SectionForm";
12
15
  const __P = {
13
16
  p3: "p-[var(--appkit-space-3)]",
14
17
  };
15
- function validateThirdPartyUrl(url, issues) {
16
- if (!url.trim()) {
17
- issues.push("Third-party ads require a third-party URL");
18
- return;
19
- }
20
- try {
21
- const parsed = new URL(url.trim());
22
- if (parsed.protocol !== "https:") {
23
- issues.push("Third-party ad URL must be https");
24
- }
25
- }
26
- catch (_err) {
27
- void normalizeError(_err);
28
- issues.push("Third-party ad URL must be a valid URL");
29
- }
30
- }
31
- function renderAdScheduleSection({ startAt, setStartAt, endAt, setEndAt }) {
32
- return (_jsxs(Div, { layout: "grid", gap: "3", className: "grid-cols-1 md:grid-cols-2", children: [_jsx(Input, { label: "Start at (ISO)", value: startAt, onChange: (event) => setStartAt(event.target.value), placeholder: "2026-05-01T00:00:00.000Z" }), _jsx(Input, { label: "End at (ISO)", value: endAt, onChange: (event) => setEndAt(event.target.value), placeholder: "2026-05-30T23:59:59.000Z" })] }));
33
- }
34
- function renderAdCreativeSection({ title, setTitle, body, setBody, imageUrl, setImageUrl, ctaLabel, setCtaLabel, ctaHref, setCtaHref, adsenseSlot, setAdsenseSlot, thirdPartyUrl, setThirdPartyUrl, provider, }) {
35
- return (_jsxs(Div, { layout: "grid", gap: "3", className: "grid-cols-1 md:grid-cols-2", children: [_jsx(Input, { label: "Creative title", value: title, onChange: (e) => setTitle(e.target.value), placeholder: "Catch the mega sale" }), _jsx(Input, { label: "Creative body", value: body, onChange: (e) => setBody(e.target.value), placeholder: "Up to 40% off this weekend" }), _jsx(Input, { label: "Image URL", value: imageUrl, onChange: (e) => setImageUrl(e.target.value), placeholder: "https://..." }), _jsx(Input, { label: "CTA label", value: ctaLabel, onChange: (e) => setCtaLabel(e.target.value), placeholder: "Shop now" }), _jsx(Input, { label: "CTA URL", value: ctaHref, onChange: (e) => setCtaHref(e.target.value), placeholder: "/promotions/deals" }), provider === "adsense" ? (_jsx(Input, { label: "AdSense slot", value: adsenseSlot, onChange: (e) => setAdsenseSlot(e.target.value), placeholder: "1234567890" })) : null, provider === "thirdParty" ? (_jsx(Input, { label: "Third-party URL", value: thirdPartyUrl, onChange: (e) => setThirdPartyUrl(e.target.value), placeholder: "https://adnetwork.example/slot" })) : null] }));
18
+ const PROVIDER_OPTIONS = [
19
+ { label: "Manual", value: "manual" },
20
+ { label: "AdSense", value: "adsense" },
21
+ { label: "Third Party", value: "thirdParty" },
22
+ ];
23
+ const STATUS_OPTIONS = [
24
+ { label: "Draft", value: "draft" },
25
+ { label: "Active", value: "active" },
26
+ { label: "Scheduled", value: "scheduled" },
27
+ { label: "Paused", value: "paused" },
28
+ ];
29
+ const EMPTY_AD = {
30
+ name: "",
31
+ provider: "manual",
32
+ status: "draft",
33
+ priority: 0,
34
+ placementIds: [],
35
+ requiresConsent: false,
36
+ startAt: "",
37
+ endAt: "",
38
+ creativeTitle: "",
39
+ creativeBody: "",
40
+ creativeImageUrl: "",
41
+ ctaLabel: "",
42
+ ctaHref: "",
43
+ adsenseSlot: "",
44
+ thirdPartyUrl: "",
45
+ };
46
+ /**
47
+ * The placement checkboxes.
48
+ *
49
+ * Its options come from a live query, so it cannot be a generated select;
50
+ * module level rather than inline for the DEEP_NESTING reason.
51
+ */
52
+ function PlacementChecklist({ placements, selected, onToggle, }) {
53
+ return (_jsx(Stack, { gap: "sm", children: _jsx(Div, { layout: "grid", gap: "2", className: "grid-cols-1 md:grid-cols-2", children: placements.map((placement) => (_jsxs("label", { className: "flex items-center gap-[var(--appkit-space-2)] rounded-md border border-[var(--appkit-color-border)] px-[var(--appkit-space-3)] py-[var(--appkit-space-2)]", children: [_jsx(Checkbox, { bare: true, checked: selected.includes(placement.id), onChange: () => onToggle(placement.id) }), _jsx(Span, { size: "sm", children: placement.label })] }, placement.id))) }) }));
36
54
  }
37
55
  export function AdminAdEditorView({ adId, endpointBase = ADMIN_ENDPOINTS.ADS, labels = {}, onSaved, ...rest }) {
38
- const [name, setName] = React.useState("");
39
- const [provider, setProvider] = React.useState("manual");
40
- const [status, setStatus] = React.useState("draft");
41
- const [priority, setPriority] = React.useState(0);
42
- const [requiresConsent, setRequiresConsent] = React.useState(false);
43
- const [selectedPlacements, setSelectedPlacements] = React.useState([]);
44
- const [startAt, setStartAt] = React.useState("");
45
- const [endAt, setEndAt] = React.useState("");
46
- const [title, setTitle] = React.useState("");
47
- const [body, setBody] = React.useState("");
48
- const [imageUrl, setImageUrl] = React.useState("");
49
- const [ctaLabel, setCtaLabel] = React.useState("");
50
- const [ctaHref, setCtaHref] = React.useState("");
51
- const [adsenseSlot, setAdsenseSlot] = React.useState("");
52
- const [thirdPartyUrl, setThirdPartyUrl] = React.useState("");
56
+ const [form, setForm] = React.useState(EMPTY_AD);
57
+ const patch = (partial) => setForm((prev) => Object.assign({}, prev, partial));
53
58
  const { showToast } = useToast();
54
59
  const metaQuery = useQuery({
55
60
  queryKey: ["admin-ads-editor-meta", endpointBase],
@@ -63,45 +68,59 @@ export function AdminAdEditorView({ adId, endpointBase = ADMIN_ENDPOINTS.ADS, la
63
68
  staleTime: 15000,
64
69
  });
65
70
  React.useEffect(() => {
66
- if (!adQuery.data?.item)
71
+ const item = adQuery.data?.item;
72
+ if (!item)
67
73
  return;
68
- const item = adQuery.data.item;
69
- setName(item.name || "");
70
- setProvider(item.provider || "manual");
71
- setStatus(item.status || "draft");
72
- setPriority(item.priority ?? 0);
73
- setRequiresConsent(Boolean(item.requiresConsent));
74
- setSelectedPlacements(item.placementIds ?? []);
75
- setStartAt(item.startAt || "");
76
- setEndAt(item.endAt || "");
77
- setTitle(item.creative?.title || "");
78
- setBody(item.creative?.body || "");
79
- setImageUrl(item.creative?.imageUrl || "");
80
- setCtaLabel(item.creative?.ctaLabel || "");
81
- setCtaHref(item.creative?.ctaHref || "");
82
- setAdsenseSlot(item.creative?.adsenseSlot || "");
83
- setThirdPartyUrl(item.creative?.thirdPartyUrl || "");
74
+ patch({
75
+ name: item.name || "",
76
+ provider: item.provider || "manual",
77
+ status: item.status || "draft",
78
+ priority: item.priority ?? 0,
79
+ requiresConsent: Boolean(item.requiresConsent),
80
+ placementIds: item.placementIds ?? [],
81
+ startAt: item.startAt || "",
82
+ endAt: item.endAt || "",
83
+ creativeTitle: item.creative?.title || "",
84
+ creativeBody: item.creative?.body || "",
85
+ creativeImageUrl: item.creative?.imageUrl || "",
86
+ ctaLabel: item.creative?.ctaLabel || "",
87
+ ctaHref: item.creative?.ctaHref || "",
88
+ adsenseSlot: item.creative?.adsenseSlot || "",
89
+ thirdPartyUrl: item.creative?.thirdPartyUrl || "",
90
+ });
91
+ // eslint-disable-next-line react-hooks/exhaustive-deps
84
92
  }, [adQuery.data]);
85
93
  const saveMutation = useApiMutation({
86
94
  errorMessage: "Save failed",
87
95
  mutationFn: async () => {
96
+ /*
97
+ * `visibleValues` is what keeps a provider-specific field out of the
98
+ * payload when its input is not on screen.
99
+ *
100
+ * Both `adsenseSlot` and `thirdPartyUrl` used to go out unconditionally
101
+ * while their inputs unmount on a provider switch — so an ad created as
102
+ * AdSense and switched to "manual" shipped a stale slot nothing could
103
+ * show or clear. The rule was then restated as a ternary right here; it
104
+ * now lives once, on the schema, as the same predicate that hides them.
105
+ */
106
+ const draft = visibleValues(adminAdFormSchema, form);
88
107
  const payload = {
89
- name,
90
- provider,
91
- status,
92
- priority,
93
- requiresConsent,
94
- placementIds: selectedPlacements,
95
- startAt: startAt || undefined,
96
- endAt: endAt || undefined,
108
+ name: draft.name,
109
+ provider: draft.provider,
110
+ status: draft.status,
111
+ priority: draft.priority,
112
+ requiresConsent: draft.requiresConsent,
113
+ placementIds: draft.placementIds,
114
+ startAt: draft.startAt || undefined,
115
+ endAt: draft.endAt || undefined,
97
116
  creative: {
98
- title: title || undefined,
99
- body: body || undefined,
100
- imageUrl: imageUrl || undefined,
101
- ctaLabel: ctaLabel || undefined,
102
- ctaHref: ctaHref || undefined,
103
- adsenseSlot: adsenseSlot || undefined,
104
- thirdPartyUrl: thirdPartyUrl || undefined,
117
+ title: draft.creativeTitle || undefined,
118
+ body: draft.creativeBody || undefined,
119
+ imageUrl: draft.creativeImageUrl || undefined,
120
+ ctaLabel: draft.ctaLabel || undefined,
121
+ ctaHref: draft.ctaHref || undefined,
122
+ adsenseSlot: draft.adsenseSlot || undefined,
123
+ thirdPartyUrl: draft.thirdPartyUrl || undefined,
105
124
  },
106
125
  };
107
126
  if (adId) {
@@ -120,49 +139,35 @@ export function AdminAdEditorView({ adId, endpointBase = ADMIN_ENDPOINTS.ADS, la
120
139
  });
121
140
  const placements = adQuery.data?.placements ?? metaQuery.data?.placements ?? [];
122
141
  const providerCredentialStatus = metaQuery.data?.providerCredentialStatus;
142
+ /*
143
+ * What remains of `publishIssues` after the schema took its share.
144
+ *
145
+ * The name, the placement count, the date order, "a scheduled ad needs a
146
+ * start date" and the three provider rules all moved to the schema, where
147
+ * they reach the field that is wrong instead of a warning banner that only
148
+ * appeared once the status was already publish-ready. What is left cannot
149
+ * live there: it depends on the LIVE placement list and on whether the
150
+ * provider's credentials are configured in site settings — neither of which
151
+ * a schema can see.
152
+ */
123
153
  const publishIssues = React.useMemo(() => {
124
154
  const issues = [];
125
155
  const now = Date.now();
126
- if (!name.trim()) {
127
- issues.push("Ad name is required");
128
- }
129
- if (selectedPlacements.length === 0) {
130
- issues.push("Select at least one placement");
131
- }
132
156
  const knownPlacementIds = new Set(placements.map((placement) => placement.id));
133
157
  const enabledPlacementIds = new Set(placements.filter((placement) => placement.enabled).map((placement) => placement.id));
134
- const unknownPlacements = selectedPlacements.filter((placementId) => !knownPlacementIds.has(placementId));
135
- if (unknownPlacements.length > 0) {
136
- issues.push(`Unknown placements: ${unknownPlacements.join(", ")}`);
137
- }
138
- const disabledPlacements = selectedPlacements.filter((placementId) => !enabledPlacementIds.has(placementId));
139
- if (disabledPlacements.length > 0) {
140
- issues.push(`Selected placements are disabled: ${disabledPlacements.join(", ")}`);
158
+ const unknown = form.placementIds.filter((id) => !knownPlacementIds.has(id));
159
+ if (unknown.length > 0)
160
+ issues.push(`Unknown placements: ${unknown.join(", ")}`);
161
+ const disabled = form.placementIds.filter((id) => !enabledPlacementIds.has(id));
162
+ if (disabled.length > 0) {
163
+ issues.push(`Selected placements are disabled: ${disabled.join(", ")}`);
141
164
  }
142
- const startMs = startAt ? new Date(startAt).getTime() : null;
143
- const endMs = endAt ? new Date(endAt).getTime() : null;
144
- if (startAt && Number.isNaN(startMs)) {
145
- issues.push("Start date must be a valid ISO timestamp");
165
+ const startMs = form.startAt ? new Date(form.startAt).getTime() : null;
166
+ const endMs = form.endAt ? new Date(form.endAt).getTime() : null;
167
+ if (form.status === "scheduled" && startMs !== null && !Number.isNaN(startMs) && startMs <= now) {
168
+ issues.push("Scheduled ads must start in the future");
146
169
  }
147
- if (endAt && Number.isNaN(endMs)) {
148
- issues.push("End date must be a valid ISO timestamp");
149
- }
150
- if (startMs !== null &&
151
- endMs !== null &&
152
- !Number.isNaN(startMs) &&
153
- !Number.isNaN(endMs) &&
154
- endMs <= startMs) {
155
- issues.push("End date must be after start date");
156
- }
157
- if (status === "scheduled") {
158
- if (!startAt || startMs === null || Number.isNaN(startMs)) {
159
- issues.push("Scheduled ads require a valid start date");
160
- }
161
- else if (startMs <= now) {
162
- issues.push("Scheduled ads must start in the future");
163
- }
164
- }
165
- if (status === "active") {
170
+ if (form.status === "active") {
166
171
  if (startMs !== null && !Number.isNaN(startMs) && startMs > now) {
167
172
  issues.push("Active ads cannot have a future start date");
168
173
  }
@@ -170,75 +175,66 @@ export function AdminAdEditorView({ adId, endpointBase = ADMIN_ENDPOINTS.ADS, la
170
175
  issues.push("Active ads cannot have an end date in the past");
171
176
  }
172
177
  }
173
- if (provider === "manual") {
174
- if (!title.trim() && !body.trim() && !imageUrl.trim() && !ctaHref.trim()) {
175
- issues.push("Manual ads need at least one creative field (title, body, image, or CTA URL)");
176
- }
178
+ if (form.provider === "adsense" && !providerCredentialStatus?.hasAdsenseClientId) {
179
+ issues.push("AdSense provider credentials are missing in ad settings");
177
180
  }
178
- if (provider === "adsense") {
179
- if (!adsenseSlot.trim()) {
180
- issues.push("AdSense ads require an AdSense slot id");
181
- }
182
- if (!providerCredentialStatus?.hasAdsenseClientId) {
183
- issues.push("AdSense provider credentials are missing in ad settings");
184
- }
185
- }
186
- if (provider === "thirdParty") {
187
- validateThirdPartyUrl(thirdPartyUrl, issues);
188
- if (!providerCredentialStatus?.hasThirdPartyScriptUrl) {
189
- issues.push("Third-party provider script URL is missing in ad settings");
190
- }
181
+ if (form.provider === "thirdParty" && !providerCredentialStatus?.hasThirdPartyScriptUrl) {
182
+ issues.push("Third-party provider script URL is missing in ad settings");
191
183
  }
192
184
  return issues;
193
185
  }, [
194
- adsenseSlot,
195
- body,
196
- ctaHref,
197
- endAt,
198
- imageUrl,
199
- name,
186
+ form.endAt,
187
+ form.placementIds,
188
+ form.provider,
189
+ form.startAt,
190
+ form.status,
200
191
  placements,
201
- provider,
202
192
  providerCredentialStatus?.hasAdsenseClientId,
203
193
  providerCredentialStatus?.hasThirdPartyScriptUrl,
204
- selectedPlacements,
205
- startAt,
206
- status,
207
- thirdPartyUrl,
208
- title,
209
194
  ]);
210
- const publishHardeningRequired = status === "active" || status === "scheduled";
195
+ const publishHardeningRequired = form.status === "active" || form.status === "scheduled";
211
196
  const blockedByPublishValidation = publishHardeningRequired && publishIssues.length > 0;
212
- const togglePlacement = (placementId) => {
213
- setSelectedPlacements((prev) => prev.includes(placementId)
214
- ? prev.filter((id) => id !== placementId)
215
- : [...prev, placementId]);
216
- };
217
- const scheduleSection = renderAdScheduleSection({ startAt, setStartAt, endAt, setEndAt });
218
- const creativeSection = renderAdCreativeSection({
219
- title, setTitle, body, setBody, imageUrl, setImageUrl,
220
- ctaLabel, setCtaLabel, ctaHref, setCtaHref,
221
- adsenseSlot, setAdsenseSlot, thirdPartyUrl, setThirdPartyUrl, provider,
197
+ const togglePlacement = (placementId) => setForm((prev) => ({
198
+ ...prev,
199
+ placementIds: prev.placementIds.includes(placementId)
200
+ ? prev.placementIds.filter((id) => id !== placementId)
201
+ : [...prev.placementIds, placementId],
202
+ }));
203
+ const sections = React.useMemo(() => buildSectionsFromSchema(adminAdFormSchema, {
204
+ options: {
205
+ provider: PROVIDER_OPTIONS,
206
+ status: STATUS_OPTIONS,
207
+ },
208
+ renderers: {
209
+ placementIds: ({ values }) => (_jsx(PlacementChecklist, { placements: placements, selected: values.placementIds, onToggle: togglePlacement })),
210
+ },
211
+ }),
212
+ // eslint-disable-next-line react-hooks/exhaustive-deps
213
+ [placements]);
214
+ const nav = useSectionFormNav(sections, form, { scope: "admin:ad-editor" });
215
+ const { shellCtx, setFieldError, clearErrors } = useFormShellState(adminAdFormSchema, {
216
+ sections: nav.sectionMeta,
217
+ onGoToSection: nav.goToSection,
218
+ fieldToSectionIndex: nav.fieldToSectionIndex,
222
219
  });
220
+ const handleSubmit = () => {
221
+ clearErrors();
222
+ const parsed = adminAdFormSchema.safeParse(visibleValues(adminAdFormSchema, form));
223
+ if (!parsed.success) {
224
+ applyZodIssues(parsed.error.issues, setFieldError);
225
+ return;
226
+ }
227
+ if (blockedByPublishValidation) {
228
+ showToast("Cannot save in publish-ready status until the readiness issues are fixed.", "warning");
229
+ return;
230
+ }
231
+ saveMutation.mutate();
232
+ };
223
233
  return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: labels.title ?? (adId ? "Edit Ad" : "Create Ad"), sections: [
224
234
  metaQuery.error ? (_jsx(Alert, { variant: "error", title: "Could not load ad metadata", children: metaQuery.error instanceof Error ? metaQuery.error.message : "Unknown error" })) : null,
225
235
  adQuery.error ? (_jsx(Alert, { variant: "error", title: "Could not load ad", children: adQuery.error instanceof Error ? adQuery.error.message : "Unknown error" })) : null,
226
- _jsxs(Form, { schema: adminAdFormSchema, onSubmit: (event) => {
227
- event.preventDefault();
228
- if (blockedByPublishValidation) {
229
- showToast("Cannot save in publish-ready status until validation issues are fixed.", "warning");
230
- return;
231
- }
232
- saveMutation.mutate();
233
- }, spacing: "md", children: [_jsx(FormErrorSummary, {}), _jsx(Input, { label: "Ad name", value: name, onChange: (event) => setName(event.target.value), placeholder: "Summer Campaign" }), _jsxs(Div, { layout: "grid", gap: "3", className: "grid-cols-1 md:grid-cols-3", children: [_jsx(Select, { label: "Provider", value: provider, options: [
234
- { label: "Manual", value: "manual" },
235
- { label: "AdSense", value: "adsense" },
236
- { label: "Third Party", value: "thirdParty" },
237
- ], onChange: (event) => setProvider(event.target.value) }), _jsx(Select, { label: "Status", value: status, options: [
238
- { label: "Draft", value: "draft" },
239
- { label: "Active", value: "active" },
240
- { label: "Scheduled", value: "scheduled" },
241
- { label: "Paused", value: "paused" },
242
- ], onChange: (event) => setStatus(event.target.value) }), _jsx(Input, { label: "Priority", type: "number", value: String(priority), onChange: (event) => setPriority(Number(event.target.value || 0)) })] }), _jsx(Toggle, { checked: requiresConsent, onChange: setRequiresConsent, label: "Require user ad-consent for this ad" }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Placements" }), _jsx(Div, { layout: "grid", gap: "2", className: "grid-cols-1 md:grid-cols-2", children: placements.map((placement) => (_jsxs("label", { className: "flex items-center gap-[var(--appkit-space-2)] rounded-md border border-[var(--appkit-color-border)] px-[var(--appkit-space-3)] py-[var(--appkit-space-2)]", children: [_jsx(Checkbox, { bare: true, checked: selectedPlacements.includes(placement.id), onChange: () => togglePlacement(placement.id) }), _jsx(Span, { size: "sm", children: placement.label })] }, placement.id))) })] }), scheduleSection, creativeSection, _jsxs(Div, { border: "default", className: __P.p3, rounded: "lg", children: [_jsx(Text, { className: "mb-1", size: "sm", weight: "medium", children: "Preview" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: title || name || "Untitled ad" }), body ? _jsx(Text, { className: "mt-1", size: "sm", children: body }) : null, ctaLabel ? _jsxs(Text, { className: "mt-1", size: "xs", children: ["CTA: ", ctaLabel, " (", ctaHref || "#", ")"] }) : null] }), publishHardeningRequired && publishIssues.length > 0 ? (_jsx(Alert, { variant: "warning", title: "Publish readiness issues", children: publishIssues.join("; ") })) : null, _jsx(FormActions, { align: "right", children: _jsx(Button, { type: "submit", disabled: saveMutation.isPending || blockedByPublishValidation || !name.trim() || selectedPlacements.length === 0, children: saveMutation.isPending ? "Saving..." : status === "active" ? "Publish ad" : adId ? "Save changes" : "Create ad" }) })] }),
236
+ _jsxs(FormShellContext.Provider, { value: shellCtx, children: [_jsx(FormErrorSummary, {}), _jsx(SectionForm, { sections: sections, values: form, onChange: patch, onSubmit: handleSubmit, schema: adminAdFormSchema, openIds: nav.openIds, onOpenChange: nav.setOpenIds, isLoading: saveMutation.isPending, submitLabel: form.status === "active" ? "Publish ad" : adId ? "Save changes" : "Create ad" })] }, "ad-form"),
237
+ _jsxs(Div, { border: "default", className: __P.p3, rounded: "lg", children: [_jsx(Text, { className: "mb-1", size: "sm", weight: "medium", children: "Preview" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: form.creativeTitle || form.name || "Untitled ad" }), form.creativeBody ? _jsx(Text, { className: "mt-1", size: "sm", children: form.creativeBody }) : null, form.ctaLabel ? (_jsxs(Text, { className: "mt-1", size: "xs", children: ["CTA: ", form.ctaLabel, " (", form.ctaHref || "#", ")"] })) : null] }, "ad-preview"),
238
+ publishHardeningRequired && publishIssues.length > 0 ? (_jsx(Alert, { variant: "warning", title: "Publish readiness issues", children: publishIssues.join("; ") }, "ad-publish-issues")) : null,
243
239
  ] }));
244
240
  }