@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
@@ -1,23 +1,55 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { Row } from "@mohasinac/appkit/ui";
4
3
  import { useApiMutation } from "@mohasinac/appkit/client";
5
4
  import React from "react";
6
5
  import { useQuery } from "@tanstack/react-query";
7
6
  import { z } from "zod";
8
- import { Button, ConfirmDeleteModal, Div, Form, Input, RichTextEditor, Select, Stack, StackedViewShell, TagInput, Text, Toggle, useToast } from "../../../ui";
9
- import { FieldInput, FormErrorSummary } from "../../../ui/forms";
7
+ import { ConfirmDeleteModal, Div, Input, RichTextEditor, Select, Stack, StackedViewShell, TagInput, Text, Toggle, useToast } from "../../../ui";
8
+ import { FieldInput, FormErrorSummary, FormShellContext, useFormShellState } from "../../../ui/forms";
9
+ import { SectionForm, useSectionFormNav } from "../../shell";
10
10
  import { apiClient } from "../../../http";
11
11
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
12
+ /**
13
+ * Matches the draft this form actually holds, and the payload it actually
14
+ * sends.
15
+ *
16
+ * `answer` was declared as `{ text, format }` while both the state and the
17
+ * request body are a plain HTML string — the route wraps it (see CLAUDE.md
18
+ * Root Cause #39). Nothing ever called `validate()`, so the mismatch was
19
+ * invisible; under `<SectionForm>`, which parses the draft on every change, it
20
+ * would have produced a permanent error on a field the user cannot fix.
21
+ * `.passthrough()` similarly hid seven unvalidated fields.
22
+ */
12
23
  const faqFormSchema = z.object({
13
24
  question: z.string().min(5, "Question must be at least 5 characters").max(500),
14
- answer: z.object({
15
- text: z.string().min(1, "Answer is required"),
16
- format: z.enum(["html", "markdown", "text"]).optional(),
17
- }).passthrough(),
25
+ slug: z
26
+ .string()
27
+ .regex(/^faq-[a-z0-9-]*$/, "Must start with 'faq-' and use lowercase letters, digits and hyphens")
28
+ .optional()
29
+ .or(z.literal("")),
30
+ answer: z.string().min(1, "Answer is required"),
18
31
  category: z.string().min(1, "Category is required"),
32
+ tags: z.array(z.string()).optional(),
33
+ order: z.number().int().min(0).optional(),
34
+ priority: z.number().int().min(0).optional(),
19
35
  isActive: z.boolean().optional(),
20
- }).passthrough();
36
+ isPinned: z.boolean().optional(),
37
+ showOnHomepage: z.boolean().optional(),
38
+ showInFooter: z.boolean().optional(),
39
+ });
40
+ const EMPTY_FAQ = {
41
+ question: "",
42
+ slug: "",
43
+ answer: "",
44
+ category: "general",
45
+ tags: [],
46
+ order: 0,
47
+ priority: 0,
48
+ isActive: true,
49
+ isPinned: false,
50
+ showOnHomepage: false,
51
+ showInFooter: false,
52
+ };
21
53
  const __P = {
22
54
  p4: "p-[var(--appkit-space-4)]",
23
55
  };
@@ -44,19 +76,17 @@ function toSlug(str) {
44
76
  // --- Component ---------------------------------------------------------------
45
77
  export function AdminFaqEditorView({ faqId, onSaved, onDeleted, embedded, ...rest }) {
46
78
  const isEdit = Boolean(faqId);
47
- const [question, setQuestion] = React.useState("");
48
- const [slug, setSlug] = React.useState("");
79
+ const [values, setValues] = React.useState(EMPTY_FAQ);
49
80
  const [slugManual, setSlugManual] = React.useState(false);
50
- const [answer, setAnswer] = React.useState("");
51
- const [category, setCategory] = React.useState("general");
52
- const [tags, setTags] = React.useState([]);
53
- const [order, setOrder] = React.useState(0);
54
- const [priority, setPriority] = React.useState(0);
55
- const [isActive, setIsActive] = React.useState(true);
56
- const [isPinned, setIsPinned] = React.useState(false);
57
- const [showOnHomepage, setShowOnHomepage] = React.useState(false);
58
- const [showInFooter, setShowInFooter] = React.useState(false);
59
81
  const [deleteConfirmOpen, setDeleteConfirmOpen] = React.useState(false);
82
+ const handleChange = React.useCallback((partial) => {
83
+ setValues((prev) => {
84
+ const next = Object.assign({}, prev, partial);
85
+ if (partial.question !== undefined && !slugManual)
86
+ next.slug = toSlug(String(partial.question));
87
+ return next;
88
+ });
89
+ }, [slugManual]);
60
90
  const { showToast } = useToast();
61
91
  // --- load existing FAQ (edit mode) ---
62
92
  const faqQuery = useQuery({
@@ -71,40 +101,37 @@ export function AdminFaqEditorView({ faqId, onSaved, onDeleted, embedded, ...res
71
101
  const f = faqQuery.data;
72
102
  if (!f)
73
103
  return;
74
- setQuestion(f.question ?? "");
75
- setSlug(f["seo.slug"] ?? f.id ?? "");
76
104
  setSlugManual(true);
77
- setAnswer(typeof f.answer === "object" ? (f.answer?.text ?? "") : (f.answer ?? ""));
78
- setCategory(f.category ?? "general");
79
- setTags(Array.isArray(f.tags) ? f.tags : []);
80
- setOrder(typeof f.order === "number" ? f.order : 0);
81
- setPriority(typeof f.priority === "number" ? f.priority : 0);
82
- setIsActive(f.isActive ?? true);
83
- setIsPinned(f.isPinned ?? false);
84
- setShowOnHomepage(f.showOnHomepage ?? false);
85
- setShowInFooter(f.showInFooter ?? false);
105
+ setValues({
106
+ question: f.question ?? "",
107
+ slug: f["seo.slug"] ?? f.id ?? "",
108
+ answer: typeof f.answer === "object" ? (f.answer?.text ?? "") : (f.answer ?? ""),
109
+ category: f.category ?? "general",
110
+ tags: Array.isArray(f.tags) ? f.tags : [],
111
+ order: typeof f.order === "number" ? f.order : 0,
112
+ priority: typeof f.priority === "number" ? f.priority : 0,
113
+ isActive: f.isActive ?? true,
114
+ isPinned: f.isPinned ?? false,
115
+ showOnHomepage: f.showOnHomepage ?? false,
116
+ showInFooter: f.showInFooter ?? false,
117
+ });
86
118
  }, [faqQuery.data]);
87
- const handleQuestionChange = (value) => {
88
- setQuestion(value);
89
- if (!slugManual)
90
- setSlug(toSlug(value));
91
- };
92
119
  // --- save ---
93
120
  const saveMutation = useApiMutation({
94
121
  errorMessage: "Failed to save FAQ.",
95
122
  mutationFn: async () => {
96
123
  const payload = {
97
- question,
98
- slug: slug || toSlug(question),
99
- answer,
100
- category,
101
- tags,
102
- order,
103
- priority,
104
- isActive,
105
- isPinned,
106
- showOnHomepage,
107
- showInFooter,
124
+ question: values.question,
125
+ slug: values.slug || toSlug(values.question),
126
+ answer: values.answer,
127
+ category: values.category,
128
+ tags: values.tags,
129
+ order: values.order,
130
+ priority: values.priority,
131
+ isActive: values.isActive,
132
+ isPinned: values.isPinned,
133
+ showOnHomepage: values.showOnHomepage,
134
+ showInFooter: values.showInFooter,
108
135
  };
109
136
  if (isEdit) {
110
137
  return apiClient.put(ADMIN_ENDPOINTS.FAQ_BY_ID(faqId), payload);
@@ -129,18 +156,45 @@ export function AdminFaqEditorView({ faqId, onSaved, onDeleted, embedded, ...res
129
156
  },
130
157
  });
131
158
  const isSubmitting = saveMutation.isPending || faqQuery.isLoading;
132
- const canSave = Boolean(question.trim()) && Boolean(answer.trim());
133
- const formSection = (_jsxs(_Fragment, { children: [_jsx(Form, { schema: faqFormSchema, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FieldInput, { name: "question", label: "Question", value: question, onChange: (v) => handleQuestionChange(v), required: true, placeholder: "e.g. How does bidding work on LetItRip?" }), _jsx(Input, { label: "Slug", value: slug, onChange: (e) => {
134
- setSlug(e.target.value);
135
- setSlugManual(true);
136
- }, placeholder: "faq-how-does-bidding-work", helperText: "Auto-generated from question. Must start with 'faq-'." }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Answer" }), _jsx(RichTextEditor, { value: answer, onChange: setAnswer, placeholder: "Write a clear, helpful answer...", minHeightClassName: "min-h-[200px]" })] }), _jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-2", children: [_jsx(Select, { label: "Category", options: CATEGORY_OPTIONS, value: category, onValueChange: setCategory }), _jsx(Input, { label: "Display order", value: String(order), onChange: (e) => setOrder(parseInt(e.target.value, 10) || 0), type: "number", min: 0, helperText: "Lower = shown first within category." })] }), _jsx(Input, { label: "Priority", value: String(priority), onChange: (e) => setPriority(parseInt(e.target.value, 10) || 0), type: "number", min: 0, helperText: "Higher priority FAQs appear first in search results." }), _jsx(TagInput, { label: "Tags", value: tags, onChange: setTags, placeholder: "e.g. shipping, pokemon, returns" }), _jsxs(Stack, { className: `${__P.p4}`, gap: "3", rounded: "lg", border: "default", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Visibility" }), _jsx(Toggle, { label: "Active (visible to users)", checked: isActive, onChange: setIsActive }), _jsx(Toggle, { label: "Pinned (always shown at top)", checked: isPinned, onChange: setIsPinned }), _jsx(Toggle, { label: "Show on homepage FAQ section", checked: showOnHomepage, onChange: setShowOnHomepage }), _jsx(Toggle, { label: "Show in footer FAQ links", checked: showInFooter, onChange: setShowInFooter })] }), _jsx(FormErrorSummary, {}), _jsxs(Row, { gap: "3", padding: "t-xs", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !canSave || isSubmitting, onClick: () => {
137
- clearErrors();
138
- if (!question.trim()) {
139
- setFieldError("question", "Question is required");
140
- return;
141
- }
142
- saveMutation.mutate();
143
- }, children: isEdit ? "Save changes" : "Create FAQ" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => setDeleteConfirmOpen(true), children: "Delete FAQ" }))] })] })) }, "faq-form"), deleteConfirmOpen && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete FAQ", message: "Delete this FAQ? This cannot be undone.", onConfirm: () => { deleteMutation.mutate(); setDeleteConfirmOpen(false); }, onClose: () => setDeleteConfirmOpen(false), isDeleting: deleteMutation.isPending }))] }));
159
+ const sections = React.useMemo(() => [
160
+ {
161
+ id: "basics",
162
+ label: "Question & answer",
163
+ required: true,
164
+ fields: ["question", "slug", "answer"],
165
+ // The rich-text editor holds an uncommitted buffer.
166
+ keepMounted: true,
167
+ render: ({ values: v, onChange, errors }) => (_jsxs(Stack, { gap: "md", children: [_jsx(FieldInput, { name: "question", label: "Question", value: String(v.question ?? ""), onChange: (val) => onChange({ question: val }), required: true, placeholder: "e.g. How does bidding work on LetItRip?", error: errors.question }), _jsx(Input, { label: "Slug", value: String(v.slug ?? ""), onChange: (e) => {
168
+ setSlugManual(true);
169
+ onChange({ slug: e.target.value });
170
+ }, placeholder: "faq-how-does-bidding-work", helperText: "Auto-generated from the question until you edit it. Must start with 'faq-'.", error: errors.slug }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Answer" }), _jsx(RichTextEditor, { value: String(v.answer ?? ""), onChange: (val) => onChange({ answer: val }), placeholder: "Write a clear, helpful answer...", minHeightClassName: "min-h-[200px]" }), errors.answer && (_jsx(Text, { size: "xs", color: "error", role: "alert", children: errors.answer }))] })] })),
171
+ },
172
+ {
173
+ id: "classification",
174
+ label: "Filing & ordering",
175
+ fields: ["category", "tags", "order", "priority"],
176
+ render: ({ values: v, onChange, errors }) => (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-2", children: [_jsx(Select, { label: "Category", options: CATEGORY_OPTIONS, value: String(v.category ?? "general"), onValueChange: (val) => onChange({ category: val }), error: errors.category }), _jsx(Input, { label: "Display order", value: String(v.order ?? 0), onChange: (e) => onChange({ order: parseInt(e.target.value, 10) || 0 }), type: "number", min: 0, helperText: "Lower = shown first within category.", error: errors.order })] }), _jsx(Input, { label: "Priority", value: String(v.priority ?? 0), onChange: (e) => onChange({ priority: parseInt(e.target.value, 10) || 0 }), type: "number", min: 0, helperText: "Higher priority FAQs appear first in search results.", error: errors.priority }), _jsx(TagInput, { label: "Tags", value: Array.isArray(v.tags) ? v.tags : [], onChange: (val) => onChange({ tags: val }), placeholder: "e.g. shipping, pokemon, returns" })] })),
177
+ },
178
+ {
179
+ id: "visibility",
180
+ label: "Visibility",
181
+ fields: ["isActive", "isPinned", "showOnHomepage", "showInFooter"],
182
+ render: ({ values: v, onChange }) => (_jsxs(Stack, { gap: "3", children: [_jsx(Toggle, { label: "Active (visible to users)", checked: v.isActive ?? true, onChange: (c) => onChange({ isActive: c }) }), _jsx(Toggle, { label: "Pinned (always shown at top)", checked: v.isPinned ?? false, onChange: (c) => onChange({ isPinned: c }) }), _jsx(Toggle, { label: "Show on homepage FAQ section", checked: v.showOnHomepage ?? false, onChange: (c) => onChange({ showOnHomepage: c }) }), _jsx(Toggle, { label: "Show in footer FAQ links", checked: v.showInFooter ?? false, onChange: (c) => onChange({ showInFooter: c }) })] })),
183
+ },
184
+ ], []);
185
+ const nav = useSectionFormNav(sections, values);
186
+ const { shellCtx } = useFormShellState(faqFormSchema, {
187
+ sections: nav.sectionMeta,
188
+ onGoToSection: nav.goToSection,
189
+ fieldToSectionIndex: nav.fieldToSectionIndex,
190
+ });
191
+ const formSection = (_jsxs(_Fragment, { children: [_jsxs(FormShellContext.Provider, { value: shellCtx, children: [_jsx(FormErrorSummary, {}), _jsx(SectionForm, { sections: sections, values: values, onChange: handleChange, onSubmit: () => saveMutation.mutate(), schema: faqFormSchema, openIds: nav.openIds, onOpenChange: nav.setOpenIds, isLoading: isSubmitting, submitLabel: isEdit ? "Save changes" : "Create FAQ", destructiveAction: isEdit
192
+ ? {
193
+ label: "Delete FAQ",
194
+ onClick: () => setDeleteConfirmOpen(true),
195
+ disabled: deleteMutation.isPending,
196
+ }
197
+ : undefined })] }), deleteConfirmOpen && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete FAQ", message: "Delete this FAQ? This cannot be undone.", onConfirm: () => { deleteMutation.mutate(); setDeleteConfirmOpen(false); }, onClose: () => setDeleteConfirmOpen(false), isDeleting: deleteMutation.isPending }))] }));
144
198
  if (embedded) {
145
199
  return _jsx(Div, { className: `${__O.yAuto} ${__P.p4}`, children: formSection });
146
200
  }
@@ -4,10 +4,13 @@ import { Row } from "@mohasinac/appkit/ui";
4
4
  import { useApiMutation } from "@mohasinac/appkit/client";
5
5
  import React from "react";
6
6
  import { useQuery } from "@tanstack/react-query";
7
- import { Button, Checkbox, ConfirmDeleteModal, Div, Form, Grid, Input, Label, Select, Stack, StackedViewShell, Text, Toggle, useToast, } from "../../../ui";
7
+ import { Checkbox, ConfirmDeleteModal, Div, Label, StackedViewShell, Text, useToast, } from "../../../ui";
8
8
  import { apiClient } from "../../../http";
9
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
10
  import { FormErrorSummary, applyZodIssues } from "../../../ui/forms";
11
+ import { FormShellContext, useFormShellState } from "../../../ui/forms/FormShell";
12
+ import { buildSectionsFromSchema, visibleValues } from "../../shell/build-sections";
13
+ import { SectionForm, useSectionFormNav } from "../../shell/SectionForm";
11
14
  import { productFeatureFormSchema } from "../../products/schemas/product-features.validators";
12
15
  import { PRODUCT_FEATURE_CATEGORY_OPTIONS, PRODUCT_FEATURE_DEFAULT_DISPLAY_ORDER, PRODUCT_FEATURE_ICON_COLOR_OPTIONS, PRODUCT_FEATURE_PRODUCT_TYPE_OPTIONS, PRODUCT_FEATURE_SCOPE_OPTIONS, } from "../../products/constants/product-features.constants";
13
16
  const __P = {
@@ -29,17 +32,20 @@ const TOAST = {
29
32
  export function AdminFeatureEditorView({ featureId, fixedScope, fixedStoreId, embedded, endpointOverride, onSaved, onDeleted, ...rest }) {
30
33
  const isEdit = Boolean(featureId);
31
34
  const { showToast } = useToast();
32
- const [label, setLabel] = React.useState("");
33
- const [description, setDescription] = React.useState("");
34
- const [icon, setIcon] = React.useState("");
35
- const [iconColor, setIconColor] = React.useState("");
36
- const [category, setCategory] = React.useState("platform");
37
- const [scope, setScope] = React.useState(fixedScope ?? "platform");
38
- const [productTypes, setProductTypes] = React.useState(["all"]);
39
- const [storeId, setStoreId] = React.useState(fixedStoreId ?? "");
40
- const [isActive, setIsActive] = React.useState(true);
35
+ const [form, setForm] = React.useState({
36
+ label: "",
37
+ description: "",
38
+ icon: "",
39
+ iconColor: "",
40
+ category: "platform",
41
+ scope: fixedScope ?? "platform",
42
+ productTypes: ["all"],
43
+ storeId: fixedStoreId ?? "",
44
+ isActive: true,
45
+ displayOrder: String(PRODUCT_FEATURE_DEFAULT_DISPLAY_ORDER),
46
+ });
41
47
  const [deleteConfirmOpen, setDeleteConfirmOpen] = React.useState(false);
42
- const [displayOrder, setDisplayOrder] = React.useState(String(PRODUCT_FEATURE_DEFAULT_DISPLAY_ORDER));
48
+ const patch = (partial) => setForm((prev) => Object.assign({}, prev, partial));
43
49
  const createEndpoint = endpointOverride?.create ?? ADMIN_ENDPOINTS.PRODUCT_FEATURES;
44
50
  const byIdEndpoint = endpointOverride?.byId ?? ADMIN_ENDPOINTS.PRODUCT_FEATURE_BY_ID;
45
51
  const featureQuery = useQuery({
@@ -57,50 +63,58 @@ export function AdminFeatureEditorView({ featureId, fixedScope, fixedStoreId, em
57
63
  const body = res?.data ?? res;
58
64
  return body?.items ?? [];
59
65
  },
60
- enabled: scope === "store" && !fixedStoreId,
66
+ enabled: form.scope === "store" && !fixedStoreId,
61
67
  });
62
68
  React.useEffect(() => {
63
69
  const f = featureQuery.data;
64
70
  if (!f)
65
71
  return;
66
- setLabel(f.label ?? "");
67
- setDescription(f.description ?? "");
68
- setIcon(f.icon ?? "");
69
- setIconColor(f.iconColor ?? "");
70
- setCategory(f.category ?? "platform");
71
- if (!fixedScope)
72
- setScope(f.scope ?? "platform");
73
- setProductTypes(f.productTypes ?? ["all"]);
74
- if (!fixedStoreId)
75
- setStoreId(f.storeId ?? "");
76
- setIsActive(f.isActive ?? true);
77
- setDisplayOrder(f.displayOrder !== undefined
78
- ? String(f.displayOrder)
79
- : String(PRODUCT_FEATURE_DEFAULT_DISPLAY_ORDER));
72
+ patch({
73
+ label: f.label ?? "",
74
+ description: f.description ?? "",
75
+ icon: f.icon ?? "",
76
+ iconColor: f.iconColor ?? "",
77
+ category: f.category ?? "platform",
78
+ // A fixed scope/store comes from the mounting page and outranks the record.
79
+ ...(fixedScope ? {} : { scope: f.scope ?? "platform" }),
80
+ productTypes: f.productTypes ?? ["all"],
81
+ ...(fixedStoreId ? {} : { storeId: f.storeId ?? "" }),
82
+ isActive: f.isActive ?? true,
83
+ displayOrder: f.displayOrder !== undefined
84
+ ? String(f.displayOrder)
85
+ : String(PRODUCT_FEATURE_DEFAULT_DISPLAY_ORDER),
86
+ });
80
87
  }, [featureQuery.data, fixedScope, fixedStoreId]);
81
88
  const toggleProductType = (value) => {
82
- setProductTypes((prev) => {
89
+ setForm((prev) => {
83
90
  if (value === "all")
84
- return ["all"];
85
- const next = prev.filter((v) => v !== "all");
86
- return next.includes(value)
87
- ? next.filter((v) => v !== value)
88
- : [...next, value];
91
+ return { ...prev, productTypes: ["all"] };
92
+ const next = prev.productTypes.filter((v) => v !== "all");
93
+ return {
94
+ ...prev,
95
+ productTypes: next.includes(value)
96
+ ? next.filter((v) => v !== value)
97
+ : [...next, value],
98
+ };
89
99
  });
90
100
  };
91
101
  const saveMutation = useApiMutation({
102
+ errorMessage: "Failed to save feature.",
92
103
  mutationFn: async () => {
104
+ // `visibleValues` drops `storeId` when the scope is not "store", so a
105
+ // store picked and then abandoned cannot ride along on a platform feature.
106
+ const draft = visibleValues(productFeatureFormSchema, form);
93
107
  const payload = {
94
- label,
95
- description: description || undefined,
96
- icon,
97
- iconColor: iconColor || undefined,
98
- category,
99
- scope,
100
- productTypes: productTypes.length === 0 ? ["all"] : productTypes,
101
- storeId: scope === "store" ? storeId || undefined : undefined,
102
- isActive,
103
- displayOrder: Number(displayOrder) || PRODUCT_FEATURE_DEFAULT_DISPLAY_ORDER,
108
+ label: form.label,
109
+ description: form.description || undefined,
110
+ icon: form.icon,
111
+ iconColor: form.iconColor || undefined,
112
+ category: form.category,
113
+ scope: form.scope,
114
+ productTypes: form.productTypes.length === 0 ? ["all"] : form.productTypes,
115
+ storeId: draft.storeId || undefined,
116
+ isActive: form.isActive,
117
+ displayOrder: Number(form.displayOrder) || PRODUCT_FEATURE_DEFAULT_DISPLAY_ORDER,
104
118
  };
105
119
  if (isEdit) {
106
120
  return apiClient.put(byIdEndpoint(featureId), payload);
@@ -117,6 +131,7 @@ export function AdminFeatureEditorView({ featureId, fixedScope, fixedStoreId, em
117
131
  },
118
132
  });
119
133
  const deleteMutation = useApiMutation({
134
+ errorMessage: "Failed to delete feature.",
120
135
  mutationFn: () => apiClient.delete(byIdEndpoint(featureId)),
121
136
  onSuccess: () => {
122
137
  showToast(TOAST.DELETED, "success");
@@ -142,32 +157,57 @@ export function AdminFeatureEditorView({ featureId, fixedScope, fixedStoreId, em
142
157
  * reports what is actually wrong, on the field itself.
143
158
  */
144
159
  const isDisabled = isSubmitting;
145
- const formSection = (_jsx(Form, { schema: productFeatureFormSchema, onSubmit: (e) => e.preventDefault(), children: ({ setFieldError, clearErrors }) => (_jsxs(Stack, { gap: "md", children: [_jsx(FormErrorSummary, {}), _jsx(Input, { label: "Label", value: label, onChange: (e) => setLabel(e.target.value), required: true, placeholder: "e.g. Free Shipping", helperText: "Shown on product cards and detail pages." }), _jsx(Input, { label: "Description", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Optional \u2014 shown as a tooltip on the badge." }), _jsxs(Grid, { cols: "halves", gap: "md", children: [_jsx(Input, { label: "Icon", value: icon, onChange: (e) => setIcon(e.target.value), required: true, placeholder: "truck", helperText: "Icon-set name key (e.g. truck, star, trophy) OR an SVG path starting with M." }), _jsx(Select, { label: "Icon colour", value: iconColor, onChange: (e) => setIconColor(e.target.value), options: PRODUCT_FEATURE_ICON_COLOR_OPTIONS })] }), _jsxs(Grid, { cols: "halves", gap: "md", children: [_jsx(Select, { label: "Category", value: category, onChange: (e) => setCategory(e.target.value), options: PRODUCT_FEATURE_CATEGORY_OPTIONS, required: true }), _jsx(Input, { label: "Display order", value: displayOrder, onChange: (e) => setDisplayOrder(e.target.value), type: "number", min: 0 })] }), _jsxs(Div, { children: [_jsx(Text, { className: FIELD_LABEL_CLASS, children: "Applies to" }), _jsx(Row, { gap: "sm", wrap: true, className: "mt-2", children: PRODUCT_FEATURE_PRODUCT_TYPE_OPTIONS.map((opt) => {
146
- const checked = productTypes.includes(opt.value);
147
- const pillClass = `${PILL_BASE_CLASS} ${checked ? PILL_CHECKED_CLASS : PILL_UNCHECKED_CLASS}`;
148
- return (_jsxs(Label, { className: pillClass, children: [_jsx(Checkbox, { bare: true, className: "sr-only", checked: checked, onChange: () => toggleProductType(opt.value) }), opt.label] }, opt.value));
149
- }) })] }), !fixedScope && (_jsx(Select, { label: "Scope", value: scope, onChange: (e) => setScope(e.target.value), options: PRODUCT_FEATURE_SCOPE_OPTIONS })), scope === "store" && !fixedStoreId && (_jsx(Select, { label: "Store", value: storeId, onChange: (e) => setStoreId(e.target.value), options: storeOptions, required: true, helperText: "Store-scope features are visible only on this store's listings." })), _jsx(Toggle, { label: "Active", checked: isActive, onChange: setIsActive }), _jsxs(Row, { gap: "3", padding: "t-xs", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: isDisabled, onClick: () => {
150
- // Parse before saving. This form had NO validation at all: the
151
- // only guard was `required` on the inputs, an HTML attribute
152
- // that any programmatic submit bypasses, and the max lengths /
153
- // displayOrder bounds / productTypes minimum were never checked
154
- // client-side at all.
155
- clearErrors();
156
- const parsed = productFeatureFormSchema.safeParse({
157
- label, description: description || undefined,
158
- icon, iconColor: iconColor || undefined,
159
- category, scope,
160
- productTypes: productTypes.length === 0 ? ["all"] : productTypes,
161
- storeId: scope === "store" ? storeId || undefined : undefined,
162
- isActive,
163
- displayOrder: Number(displayOrder) || PRODUCT_FEATURE_DEFAULT_DISPLAY_ORDER,
164
- });
165
- if (!parsed.success) {
166
- applyZodIssues(parsed.error.issues, setFieldError);
167
- return;
168
- }
169
- saveMutation.mutate();
170
- }, children: isEdit ? "Save changes" : "Create feature" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => setDeleteConfirmOpen(true), children: "Delete" }))] })] })) }, "feature-form"));
160
+ const sections = React.useMemo(() => buildSectionsFromSchema(productFeatureFormSchema, {
161
+ options: {
162
+ iconColor: [...PRODUCT_FEATURE_ICON_COLOR_OPTIONS],
163
+ category: [...PRODUCT_FEATURE_CATEGORY_OPTIONS],
164
+ scope: [...PRODUCT_FEATURE_SCOPE_OPTIONS],
165
+ storeId: storeOptions,
166
+ },
167
+ renderers: {
168
+ /*
169
+ * A pill group, not a list editor. `kind: "list"` renders the
170
+ * generator's disabled placeholder, which for a required multi-select
171
+ * would be an unsubmittable form.
172
+ */
173
+ productTypes: ({ values }) => (_jsxs(Div, { children: [_jsx(Text, { className: FIELD_LABEL_CLASS, children: "Applies to" }), _jsx(Row, { gap: "sm", wrap: true, className: "mt-2", children: PRODUCT_FEATURE_PRODUCT_TYPE_OPTIONS.map((opt) => {
174
+ const checked = values.productTypes.includes(opt.value);
175
+ const pillClass = `${PILL_BASE_CLASS} ${checked ? PILL_CHECKED_CLASS : PILL_UNCHECKED_CLASS}`;
176
+ return (_jsxs(Label, { className: pillClass, children: [_jsx(Checkbox, { bare: true, className: "sr-only", checked: checked, onChange: () => toggleProductType(opt.value) }), opt.label] }, opt.value));
177
+ }) })] })),
178
+ /*
179
+ * `fixedScope` / `fixedStoreId` come from the MOUNTING PAGE, not from
180
+ * the draft, so they cannot be `when` predicates — those see values
181
+ * only. Returning null is how a prop-driven hide is expressed.
182
+ */
183
+ ...(fixedScope ? { scope: () => null } : {}),
184
+ ...(fixedStoreId ? { storeId: () => null } : {}),
185
+ },
186
+ }), [storeOptions, fixedScope, fixedStoreId]);
187
+ const nav = useSectionFormNav(sections, form, { scope: "admin:feature-editor" });
188
+ const { shellCtx, setFieldError, clearErrors } = useFormShellState(productFeatureFormSchema, {
189
+ sections: nav.sectionMeta,
190
+ onGoToSection: nav.goToSection,
191
+ fieldToSectionIndex: nav.fieldToSectionIndex,
192
+ });
193
+ const onSubmit = () => {
194
+ clearErrors();
195
+ const draft = visibleValues(productFeatureFormSchema, form);
196
+ const parsed = productFeatureFormSchema.safeParse({
197
+ ...draft,
198
+ description: form.description || undefined,
199
+ iconColor: form.iconColor || undefined,
200
+ productTypes: form.productTypes.length === 0 ? ["all"] : form.productTypes,
201
+ storeId: draft.storeId || undefined,
202
+ displayOrder: Number(form.displayOrder) || PRODUCT_FEATURE_DEFAULT_DISPLAY_ORDER,
203
+ });
204
+ if (!parsed.success) {
205
+ applyZodIssues(parsed.error.issues, setFieldError);
206
+ return;
207
+ }
208
+ saveMutation.mutate();
209
+ };
210
+ const formSection = (_jsxs(FormShellContext.Provider, { value: shellCtx, children: [_jsx(FormErrorSummary, {}), _jsx(SectionForm, { sections: sections, values: form, onChange: patch, onSubmit: onSubmit, schema: productFeatureFormSchema, openIds: nav.openIds, onOpenChange: nav.setOpenIds, isLoading: isSubmitting, submitLabel: isEdit ? "Save changes" : "Create feature", destructiveAction: isEdit ? { label: "Delete", onClick: () => setDeleteConfirmOpen(true) } : undefined })] }));
171
211
  const deleteModal = deleteConfirmOpen && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete Feature", message: DELETE_CONFIRM_TEXT, onConfirm: () => { deleteMutation.mutate(); setDeleteConfirmOpen(false); }, onClose: () => setDeleteConfirmOpen(false), isDeleting: deleteMutation.isPending }));
172
212
  if (embedded) {
173
213
  return _jsxs(Div, { className: `${__O.yAuto} ${__P.p4}`, children: [formSection, deleteModal] });
@@ -1,13 +1,22 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useApiMutation } from "@mohasinac/appkit/client";
4
4
  import React from "react";
5
- import { Button, Form, FormActions, Input, Select, SideDrawer, Toggle, useToast, } from "../../../ui";
5
+ import { Div, SideDrawer, useToast } from "../../../ui";
6
+ import { FormShellContext, useFormShellState } from "../../../ui/forms/FormShell";
7
+ import { SectionForm, useSectionFormNav, buildSectionsFromSchema } from "../../shell";
8
+ const EMPTY_NAV_ITEM = {
9
+ label: "",
10
+ href: "",
11
+ icon: "",
12
+ order: 0,
13
+ parentId: "",
14
+ isVisible: true,
15
+ };
6
16
  import { FormErrorSummary, applyZodIssues } from "../../../ui/forms";
7
17
  import { navItemFormSchema } from "../schemas/nav-item-form";
8
18
  import { apiClient } from "../../../http";
9
19
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
- import { ROUTES } from "../../../next/routing/route-map";
11
20
  // --- Constants ---------------------------------------------------------------
12
21
  const ICON_OPTIONS = [
13
22
  { label: "None", value: "" },
@@ -26,28 +35,23 @@ const ICON_OPTIONS = [
26
35
  // --- Component ---------------------------------------------------------------
27
36
  export function AdminNavEditorView({ open, onClose, onSaved, item, parentOptions = [], }) {
28
37
  const isEdit = Boolean(item?.id);
29
- const [label, setLabel] = React.useState("");
30
- const [href, setHref] = React.useState("");
31
- const [icon, setIcon] = React.useState("");
32
- const [order, setOrder] = React.useState(0);
33
- const [parentId, setParentId] = React.useState("");
34
- const [isVisible, setIsVisible] = React.useState(true);
38
+ const [values, setValues] = React.useState(EMPTY_NAV_ITEM);
39
+ const handleChange = React.useCallback((partial) => {
40
+ setValues((prev) => Object.assign({}, prev, partial));
41
+ }, []);
35
42
  React.useEffect(() => {
36
43
  if (open && item) {
37
- setLabel(item.label ?? "");
38
- setHref(item.href ?? "");
39
- setIcon(item.icon ?? "");
40
- setOrder(item.order ?? 0);
41
- setParentId(item.parentId ?? "");
42
- setIsVisible(item.isVisible ?? true);
44
+ setValues({
45
+ label: item.label ?? "",
46
+ href: item.href ?? "",
47
+ icon: item.icon ?? "",
48
+ order: item.order ?? 0,
49
+ parentId: item.parentId ?? "",
50
+ isVisible: item.isVisible ?? true,
51
+ });
43
52
  }
44
53
  else if (!open) {
45
- setLabel("");
46
- setHref("");
47
- setIcon("");
48
- setOrder(0);
49
- setParentId("");
50
- setIsVisible(true);
54
+ setValues(EMPTY_NAV_ITEM);
51
55
  }
52
56
  }, [open, item]);
53
57
  const { showToast } = useToast();
@@ -55,12 +59,12 @@ export function AdminNavEditorView({ open, onClose, onSaved, item, parentOptions
55
59
  errorMessage: "Failed to save nav item.",
56
60
  mutationFn: async () => {
57
61
  const payload = {
58
- label,
59
- href,
60
- icon: icon || undefined,
61
- order,
62
- parentId: parentId || undefined,
63
- isVisible,
62
+ label: values.label,
63
+ href: values.href,
64
+ icon: values.icon || undefined,
65
+ order: values.order,
66
+ parentId: values.parentId || undefined,
67
+ isVisible: values.isVisible,
64
68
  };
65
69
  if (isEdit) {
66
70
  return apiClient.patch(ADMIN_ENDPOINTS.NAVIGATION_BY_ID(item.id), payload);
@@ -73,21 +77,42 @@ export function AdminNavEditorView({ open, onClose, onSaved, item, parentOptions
73
77
  onClose();
74
78
  },
75
79
  });
76
- const canSave = Boolean(label.trim()) && Boolean(href.trim());
77
- const parentSelectOptions = [
78
- { label: "None (top-level)", value: "" },
79
- ...parentOptions,
80
- ];
81
- return (_jsx(SideDrawer, { isOpen: open, onClose: onClose, title: isEdit ? "Edit Nav Item" : "New Nav Item", children: _jsx(Form, { schema: navItemFormSchema, onSubmit: (e) => e.preventDefault(), spacing: "md", padding: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FormErrorSummary, {}), _jsx(Input, { label: "Label", value: label, onChange: (e) => setLabel(e.target.value), required: true, placeholder: "e.g. Products" }), _jsx(Input, { label: "URL / href", value: href, onChange: (e) => setHref(e.target.value), required: true, placeholder: String(ROUTES.PUBLIC.PRODUCTS), helperText: "Use relative paths (e.g. /products) or full URLs." }), _jsx(Select, { label: "Icon (optional)", options: ICON_OPTIONS, value: icon, onValueChange: setIcon }), parentOptions.length > 0 && (_jsx(Select, { label: "Parent item (for nested nav)", options: parentSelectOptions, value: parentId, onValueChange: setParentId })), _jsx(Input, { label: "Order", value: String(order), onChange: (e) => setOrder(parseInt(e.target.value, 10) || 0), type: "number", min: 0, helperText: "Lower numbers appear first." }), _jsx(Toggle, { label: "Visible in navigation", checked: isVisible, onChange: setIsVisible }), _jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "secondary", onClick: onClose, children: "Cancel" }), _jsx(Button, { type: "submit", isLoading: saveMutation.isPending, disabled: saveMutation.isPending, onClick: () => {
82
- clearErrors();
83
- const parsed = navItemFormSchema.safeParse({
84
- label, href, icon: icon || undefined, order,
85
- parentId: parentId || undefined, isVisible,
86
- });
87
- if (!parsed.success) {
88
- applyZodIssues(parsed.error.issues, setFieldError);
89
- return;
90
- }
91
- saveMutation.mutate();
92
- }, children: isEdit ? "Save changes" : "Create item" })] })] })) }) }));
80
+ const sections = React.useMemo(() => buildSectionsFromSchema(navItemFormSchema, {
81
+ options: {
82
+ icon: ICON_OPTIONS,
83
+ // Injected by the caller, so it cannot come from the schema.
84
+ parentId: [{ label: "None (top-level)", value: "" }, ...parentOptions],
85
+ },
86
+ }), [parentOptions]);
87
+ const nav = useSectionFormNav(sections, values);
88
+ const { shellCtx, setFieldError, clearErrors } = useFormShellState(navItemFormSchema, {
89
+ sections: nav.sectionMeta,
90
+ onGoToSection: nav.goToSection,
91
+ fieldToSectionIndex: nav.fieldToSectionIndex,
92
+ });
93
+ /*
94
+ * The routes already validated a nav item; this form did not, so an invalid
95
+ * href surfaced as a 400 banner after a round-trip instead of an error on the
96
+ * field. Same schema both sides now — and since it actually runs, the
97
+ * hand-rolled `canSave` that used to sit here is gone. It restated two of the
98
+ * schema's rules, disagreed with it on the rest, and was not even wired to
99
+ * the button's `disabled`.
100
+ */
101
+ const onSubmit = () => {
102
+ clearErrors();
103
+ const parsed = navItemFormSchema.safeParse({
104
+ label: values.label,
105
+ href: values.href,
106
+ icon: values.icon || undefined,
107
+ order: values.order,
108
+ parentId: values.parentId || undefined,
109
+ isVisible: values.isVisible,
110
+ });
111
+ if (!parsed.success) {
112
+ applyZodIssues(parsed.error.issues, setFieldError);
113
+ return;
114
+ }
115
+ saveMutation.mutate();
116
+ };
117
+ return (_jsx(SideDrawer, { isOpen: open, onClose: onClose, title: isEdit ? "Edit Nav Item" : "New Nav Item", children: _jsx(FormShellContext.Provider, { value: shellCtx, children: _jsxs(Div, { padding: "md", children: [_jsx(FormErrorSummary, {}), _jsx(SectionForm, { sections: sections, values: values, onChange: handleChange, onSubmit: onSubmit, schema: navItemFormSchema, openIds: nav.openIds, onOpenChange: nav.setOpenIds, isLoading: saveMutation.isPending, submitLabel: isEdit ? "Save changes" : "Create item", onCancel: onClose, cancelLabel: "Cancel" })] }) }) }));
93
118
  }