@mohasinac/appkit 2.7.59 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (324) hide show
  1. package/dist/_internal/client/features/filters/filter-load-options.d.ts +4 -5
  2. package/dist/_internal/server/features/auctions/og.js +8 -32
  3. package/dist/_internal/server/features/blog/og.js +10 -49
  4. package/dist/_internal/server/features/checkout/actions.js +6 -0
  5. package/dist/_internal/server/features/classified/og.js +9 -51
  6. package/dist/_internal/server/features/digital-code/actions.d.ts +11 -0
  7. package/dist/_internal/server/features/digital-code/actions.js +30 -0
  8. package/dist/_internal/server/features/digital-code/index.d.ts +1 -0
  9. package/dist/_internal/server/features/digital-code/index.js +1 -0
  10. package/dist/_internal/server/features/digital-code/og.js +9 -51
  11. package/dist/_internal/server/features/events/og.js +10 -49
  12. package/dist/_internal/server/features/item-requests/data.d.ts +17 -0
  13. package/dist/_internal/server/features/item-requests/data.js +20 -0
  14. package/dist/_internal/server/features/item-requests/index.d.ts +2 -0
  15. package/dist/_internal/server/features/item-requests/index.js +2 -0
  16. package/dist/_internal/server/features/item-requests/og.d.ts +40 -0
  17. package/dist/_internal/server/features/item-requests/og.js +136 -0
  18. package/dist/_internal/server/features/live/actions.d.ts +5 -0
  19. package/dist/_internal/server/features/live/actions.js +24 -0
  20. package/dist/_internal/server/features/live/index.d.ts +1 -0
  21. package/dist/_internal/server/features/live/index.js +1 -0
  22. package/dist/_internal/server/features/live/og.js +9 -51
  23. package/dist/_internal/server/features/media/contextGuards.js +19 -8
  24. package/dist/_internal/server/features/pre-orders/og.js +8 -32
  25. package/dist/_internal/server/features/prize-draws/data.d.ts +17 -0
  26. package/dist/_internal/server/features/prize-draws/data.js +20 -0
  27. package/dist/_internal/server/features/prize-draws/index.d.ts +2 -0
  28. package/dist/_internal/server/features/prize-draws/index.js +2 -0
  29. package/dist/_internal/server/features/prize-draws/og.d.ts +30 -0
  30. package/dist/_internal/server/features/prize-draws/og.js +50 -0
  31. package/dist/_internal/server/features/products/og.js +8 -33
  32. package/dist/_internal/server/features/seo/og-layout.d.ts +42 -0
  33. package/dist/_internal/server/features/seo/og-layout.js +69 -0
  34. package/dist/_internal/server/features/stores/og.js +8 -41
  35. package/dist/_internal/server/features/sublisting-categories/og.js +11 -42
  36. package/dist/client.d.ts +10 -6
  37. package/dist/client.js +7 -3
  38. package/dist/constants/index.d.ts +4 -0
  39. package/dist/constants/index.js +2 -0
  40. package/dist/constants/notification-types.d.ts +20 -0
  41. package/dist/constants/notification-types.js +37 -0
  42. package/dist/constants/pagination-presets.d.ts +39 -0
  43. package/dist/constants/pagination-presets.js +38 -0
  44. package/dist/errors/messages.d.ts +0 -4
  45. package/dist/errors/messages.js +0 -4
  46. package/dist/features/about/components/PublicProfileView.js +8 -8
  47. package/dist/features/account/components/UserOffersPanel.js +1 -1
  48. package/dist/features/account/components/UserSupportView.d.ts +1 -2
  49. package/dist/features/account/components/UserSupportView.js +9 -1
  50. package/dist/features/admin/actions/admin-actions.js +16 -1
  51. package/dist/features/admin/components/AdminAllEventEntriesView.d.ts +2 -3
  52. package/dist/features/admin/components/AdminAllEventEntriesView.js +50 -83
  53. package/dist/features/admin/components/AdminBidsView.d.ts +2 -3
  54. package/dist/features/admin/components/AdminBidsView.js +64 -98
  55. package/dist/features/admin/components/AdminBlogView.d.ts +3 -7
  56. package/dist/features/admin/components/AdminBlogView.js +53 -83
  57. package/dist/features/admin/components/AdminBrandsView.d.ts +2 -3
  58. package/dist/features/admin/components/AdminBrandsView.js +84 -83
  59. package/dist/features/admin/components/AdminBundlesView.js +82 -125
  60. package/dist/features/admin/components/AdminCarouselView.d.ts +2 -2
  61. package/dist/features/admin/components/AdminCarouselView.js +17 -52
  62. package/dist/features/admin/components/AdminCartsView.d.ts +2 -3
  63. package/dist/features/admin/components/AdminCartsView.js +39 -88
  64. package/dist/features/admin/components/AdminCategoriesView.d.ts +3 -3
  65. package/dist/features/admin/components/AdminCategoriesView.js +98 -92
  66. package/dist/features/admin/components/AdminCategoryEditorView.js +2 -2
  67. package/dist/features/admin/components/AdminClassifiedView.d.ts +3 -0
  68. package/dist/features/admin/components/AdminClassifiedView.js +44 -0
  69. package/dist/features/admin/components/AdminContactView.d.ts +2 -2
  70. package/dist/features/admin/components/AdminContactView.js +104 -111
  71. package/dist/features/admin/components/AdminCouponsView.d.ts +3 -6
  72. package/dist/features/admin/components/AdminCouponsView.js +66 -86
  73. package/dist/features/admin/components/AdminDigitalCodesView.d.ts +3 -0
  74. package/dist/features/admin/components/AdminDigitalCodesView.js +51 -0
  75. package/dist/features/admin/components/AdminFaqsView.d.ts +4 -4
  76. package/dist/features/admin/components/AdminFaqsView.js +77 -33
  77. package/dist/features/admin/components/AdminFeaturesView.d.ts +2 -3
  78. package/dist/features/admin/components/AdminFeaturesView.js +57 -70
  79. package/dist/features/admin/components/AdminGroupedListingsView.d.ts +3 -0
  80. package/dist/features/admin/components/AdminGroupedListingsView.js +43 -0
  81. package/dist/features/admin/components/AdminHistoryView.d.ts +2 -3
  82. package/dist/features/admin/components/AdminHistoryView.js +35 -52
  83. package/dist/features/admin/components/AdminListingScaffold.d.ts +2 -2
  84. package/dist/features/admin/components/AdminListingScaffold.js +3 -3
  85. package/dist/features/admin/components/AdminLiveView.d.ts +3 -0
  86. package/dist/features/admin/components/AdminLiveView.js +50 -0
  87. package/dist/features/admin/components/AdminNewsletterView.d.ts +2 -2
  88. package/dist/features/admin/components/AdminNewsletterView.js +67 -97
  89. package/dist/features/admin/components/AdminNotificationsView.d.ts +2 -3
  90. package/dist/features/admin/components/AdminNotificationsView.js +67 -93
  91. package/dist/features/admin/components/AdminOrderEditorView.js +10 -10
  92. package/dist/features/admin/components/AdminOrdersView.d.ts +2 -3
  93. package/dist/features/admin/components/AdminOrdersView.js +93 -107
  94. package/dist/features/admin/components/AdminPayoutsView.d.ts +2 -3
  95. package/dist/features/admin/components/AdminPayoutsView.js +51 -91
  96. package/dist/features/admin/components/AdminPrizeDrawsView.d.ts +2 -3
  97. package/dist/features/admin/components/AdminPrizeDrawsView.js +39 -76
  98. package/dist/features/admin/components/AdminProductEditorView.js +3 -3
  99. package/dist/features/admin/components/AdminProductsView.d.ts +4 -4
  100. package/dist/features/admin/components/AdminProductsView.js +167 -163
  101. package/dist/features/admin/components/AdminReturnRequestsView.d.ts +1 -1
  102. package/dist/features/admin/components/AdminReturnRequestsView.js +64 -66
  103. package/dist/features/admin/components/AdminReviewsView.d.ts +3 -7
  104. package/dist/features/admin/components/AdminReviewsView.js +94 -110
  105. package/dist/features/admin/components/AdminScammersView.d.ts +2 -3
  106. package/dist/features/admin/components/AdminScammersView.js +53 -97
  107. package/dist/features/admin/components/AdminSectionsView.js +1 -1
  108. package/dist/features/admin/components/AdminSessionsView.d.ts +2 -3
  109. package/dist/features/admin/components/AdminSessionsView.js +62 -89
  110. package/dist/features/admin/components/AdminSiteSettingsView.js +4 -5
  111. package/dist/features/admin/components/AdminStoreAddressesView.d.ts +1 -1
  112. package/dist/features/admin/components/AdminStoreAddressesView.js +39 -53
  113. package/dist/features/admin/components/AdminStoreEditorView.js +21 -8
  114. package/dist/features/admin/components/AdminStoresView.d.ts +2 -3
  115. package/dist/features/admin/components/AdminStoresView.js +67 -99
  116. package/dist/features/admin/components/AdminSublistingCategoriesView.d.ts +1 -1
  117. package/dist/features/admin/components/AdminSublistingCategoriesView.js +28 -44
  118. package/dist/features/admin/components/AdminSupportTicketDetailView.d.ts +10 -1
  119. package/dist/features/admin/components/AdminSupportTicketDetailView.js +191 -5
  120. package/dist/features/admin/components/AdminSupportTicketsView.d.ts +2 -3
  121. package/dist/features/admin/components/AdminSupportTicketsView.js +65 -96
  122. package/dist/features/admin/components/AdminTeamView.d.ts +2 -2
  123. package/dist/features/admin/components/AdminTeamView.js +40 -74
  124. package/dist/features/admin/components/AdminUserEditorView.d.ts +11 -1
  125. package/dist/features/admin/components/AdminUserEditorView.js +77 -7
  126. package/dist/features/admin/components/AdminUsersView.d.ts +2 -3
  127. package/dist/features/admin/components/AdminUsersView.js +108 -100
  128. package/dist/features/admin/components/AdminViewCards.d.ts +1 -1
  129. package/dist/features/admin/components/AdminViewCards.js +15 -15
  130. package/dist/features/admin/components/AdminWishlistsView.d.ts +2 -3
  131. package/dist/features/admin/components/AdminWishlistsView.js +36 -62
  132. package/dist/features/admin/components/DataListingView.d.ts +122 -0
  133. package/dist/features/admin/components/DataListingView.js +66 -0
  134. package/dist/features/admin/components/index.d.ts +10 -4
  135. package/dist/features/admin/components/index.js +6 -2
  136. package/dist/features/admin/constants/filter-tabs.d.ts +23 -17
  137. package/dist/features/admin/constants/filter-tabs.js +21 -17
  138. package/dist/features/admin/hooks/useAdminListing.d.ts +1 -0
  139. package/dist/features/admin/hooks/useAdminListing.js +1 -1
  140. package/dist/features/admin/schemas/firestore.js +6 -7
  141. package/dist/features/auctions/server.d.ts +0 -1
  142. package/dist/features/auctions/server.js +0 -1
  143. package/dist/features/auth/actions/realtime-token-actions.js +1 -1
  144. package/dist/features/auth/repository/user.repository.js +1 -1
  145. package/dist/features/blog/components/BlogFeaturedCard.js +1 -0
  146. package/dist/features/cart/repository/cart.repository.d.ts +4 -1
  147. package/dist/features/cart/repository/cart.repository.js +12 -0
  148. package/dist/features/checkout/hooks/useCheckoutApi.d.ts +0 -2
  149. package/dist/features/checkout/index.d.ts +1 -1
  150. package/dist/features/events/components/AdminEventEditorView.js +10 -13
  151. package/dist/features/events/components/AdminEventsView.d.ts +3 -3
  152. package/dist/features/events/components/AdminEventsView.js +50 -86
  153. package/dist/features/events/components/EventCard.js +1 -0
  154. package/dist/features/events/components/EventOfferCard.d.ts +19 -0
  155. package/dist/features/events/components/EventOfferCard.js +29 -0
  156. package/dist/features/events/components/EventRaffleEntryForm.d.ts +25 -0
  157. package/dist/features/events/components/EventRaffleEntryForm.js +37 -0
  158. package/dist/features/events/components/EventRafflesSection.d.ts +1 -4
  159. package/dist/features/events/components/EventRafflesSection.js +17 -6
  160. package/dist/features/events/components/EventSaleBanner.d.ts +21 -0
  161. package/dist/features/events/components/EventSaleBanner.js +18 -0
  162. package/dist/features/events/components/index.d.ts +6 -0
  163. package/dist/features/events/components/index.js +4 -0
  164. package/dist/features/events/repository/event-entry.repository.d.ts +5 -0
  165. package/dist/features/events/repository/event-entry.repository.js +7 -0
  166. package/dist/features/faq/components/FAQPageContent.js +1 -0
  167. package/dist/features/filters/AsyncFacetSection.d.ts +1 -1
  168. package/dist/features/homepage/components/AdvertisementBanner.js +6 -6
  169. package/dist/features/homepage/components/FAQSection.d.ts +0 -5
  170. package/dist/features/homepage/components/HomepageSkeleton.js +2 -2
  171. package/dist/features/homepage/components/WelcomeSection.js +3 -3
  172. package/dist/features/homepage/lib/live-stats.js +1 -1
  173. package/dist/features/homepage/schemas/firestore.d.ts +0 -2
  174. package/dist/features/layout/TitleBar.js +1 -0
  175. package/dist/features/layout/index.d.ts +0 -2
  176. package/dist/features/layout/index.js +0 -1
  177. package/dist/features/pre-orders/server.d.ts +0 -1
  178. package/dist/features/pre-orders/server.js +0 -1
  179. package/dist/features/products/components/GroupSettingsPanel.js +2 -2
  180. package/dist/features/products/components/PrizeDrawItemsEditor.js +2 -2
  181. package/dist/features/products/components/PrizeDrawsSection.d.ts +1 -4
  182. package/dist/features/products/components/PrizeDrawsSection.js +17 -6
  183. package/dist/features/products/components/PrizeRevealModal.js +1 -1
  184. package/dist/features/products/components/ProductCardMetadataSection.d.ts +11 -0
  185. package/dist/features/products/components/ProductCardMetadataSection.js +25 -0
  186. package/dist/features/products/components/ProductDetailPageView.js +3 -1
  187. package/dist/features/products/components/ProductFeaturesSelector.js +1 -1
  188. package/dist/features/products/components/ProductGradingTab.d.ts +13 -0
  189. package/dist/features/products/components/ProductGradingTab.js +25 -0
  190. package/dist/features/products/components/ProductGrid.js +15 -2
  191. package/dist/features/products/components/ProductTabsShell.d.ts +5 -1
  192. package/dist/features/products/components/ProductTabsShell.js +6 -1
  193. package/dist/features/products/components/index.d.ts +4 -0
  194. package/dist/features/products/components/index.js +3 -0
  195. package/dist/features/products/constants/action-defs.d.ts +5 -0
  196. package/dist/features/products/constants/action-defs.js +52 -32
  197. package/dist/features/products/constants/sieve.d.ts +24 -4
  198. package/dist/features/products/constants/sieve.js +20 -11
  199. package/dist/features/products/schemas/firestore.d.ts +17 -0
  200. package/dist/features/products/schemas/product-features.validators.d.ts +2 -2
  201. package/dist/features/products/schemas/product-templates.d.ts +2 -4
  202. package/dist/features/products/types/index.d.ts +4 -0
  203. package/dist/features/promotions/components/CouponsIndexListing.js +4 -4
  204. package/dist/features/promotions/server.d.ts +0 -1
  205. package/dist/features/promotions/server.js +0 -1
  206. package/dist/features/reviews/components/ReviewDetailShell.js +3 -3
  207. package/dist/features/search/api/route.js +44 -1
  208. package/dist/features/search/components/Search.js +2 -0
  209. package/dist/features/search/hooks/useNavSuggestions.d.ts +5 -1
  210. package/dist/features/search/hooks/useNavSuggestions.js +31 -4
  211. package/dist/features/seller/components/BrandInlineSelect.js +2 -2
  212. package/dist/features/seller/components/CategoryInlineSelect.js +2 -2
  213. package/dist/features/seller/components/SellerAddressesView.js +4 -5
  214. package/dist/features/seller/components/SellerAuctionsView.d.ts +3 -3
  215. package/dist/features/seller/components/SellerAuctionsView.js +60 -90
  216. package/dist/features/seller/components/SellerBidsView.js +1 -1
  217. package/dist/features/seller/components/SellerBundlesView.js +64 -89
  218. package/dist/features/seller/components/SellerClassifiedView.js +67 -90
  219. package/dist/features/seller/components/SellerCouponEditorView.js +1 -1
  220. package/dist/features/seller/components/SellerCouponsView.d.ts +2 -2
  221. package/dist/features/seller/components/SellerCouponsView.js +45 -84
  222. package/dist/features/seller/components/SellerDigitalCodesView.js +63 -88
  223. package/dist/features/seller/components/SellerGroupedListingsView.js +33 -49
  224. package/dist/features/seller/components/SellerLiveView.js +63 -84
  225. package/dist/features/seller/components/SellerOffersPanel.js +1 -1
  226. package/dist/features/seller/components/SellerOffersView.d.ts +2 -2
  227. package/dist/features/seller/components/SellerOffersView.js +37 -69
  228. package/dist/features/seller/components/SellerOrdersView.d.ts +2 -2
  229. package/dist/features/seller/components/SellerOrdersView.js +4 -4
  230. package/dist/features/seller/components/SellerPayoutMethodsView.js +1 -1
  231. package/dist/features/seller/components/SellerPayoutsView.d.ts +2 -2
  232. package/dist/features/seller/components/SellerPayoutsView.js +34 -68
  233. package/dist/features/seller/components/SellerPreOrdersView.d.ts +2 -2
  234. package/dist/features/seller/components/SellerPreOrdersView.js +49 -82
  235. package/dist/features/seller/components/SellerPrizeDrawsView.d.ts +2 -2
  236. package/dist/features/seller/components/SellerPrizeDrawsView.js +49 -82
  237. package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
  238. package/dist/features/seller/components/SellerProductsView.d.ts +2 -2
  239. package/dist/features/seller/components/SellerProductsView.js +15 -10
  240. package/dist/features/seller/components/SellerShippingConfigsView.js +1 -1
  241. package/dist/features/seller/components/SellerSidebar.d.ts +0 -4
  242. package/dist/features/seller/components/SellerSidebar.js +0 -2
  243. package/dist/features/seller/components/SellerStoreCategoriesView.js +1 -1
  244. package/dist/features/seller/components/SellerTemplatesView.js +2 -2
  245. package/dist/features/seller/components/SellersListView.d.ts +1 -2
  246. package/dist/features/seller/components/analytics/SellerAnalyticsStats.d.ts +5 -0
  247. package/dist/features/seller/components/analytics/SellerAnalyticsStats.js +2 -8
  248. package/dist/features/seller/components/index.d.ts +0 -3
  249. package/dist/features/seller/components/index.js +0 -2
  250. package/dist/features/seller/server.d.ts +0 -1
  251. package/dist/features/seller/server.js +0 -1
  252. package/dist/features/shell/FormShell.d.ts +1 -1
  253. package/dist/features/shell/FormShell.js +7 -4
  254. package/dist/features/shell/field-groups/ImageFieldGroup.d.ts +18 -0
  255. package/dist/features/shell/field-groups/ImageFieldGroup.js +15 -0
  256. package/dist/features/shell/field-groups/SeoFieldGroup.d.ts +22 -0
  257. package/dist/features/shell/field-groups/SeoFieldGroup.js +16 -0
  258. package/dist/features/shell/field-groups/StatusFieldGroup.d.ts +35 -0
  259. package/dist/features/shell/field-groups/StatusFieldGroup.js +16 -0
  260. package/dist/features/shell/field-groups/TitleDescriptionGroup.d.ts +40 -0
  261. package/dist/features/shell/field-groups/TitleDescriptionGroup.js +16 -0
  262. package/dist/features/shell/field-groups/index.d.ts +13 -0
  263. package/dist/features/shell/field-groups/index.js +9 -0
  264. package/dist/features/shell/index.d.ts +2 -0
  265. package/dist/features/shell/index.js +1 -0
  266. package/dist/features/stores/components/StoreCapabilitiesGuideView.d.ts +1 -2
  267. package/dist/features/stores/components/StoreFinanceGuideView.d.ts +1 -2
  268. package/dist/features/stores/components/StoreHeader.js +2 -1
  269. package/dist/features/stores/components/StoreListingsGuideView.d.ts +1 -2
  270. package/dist/features/stores/components/StoreOrdersGuideView.d.ts +1 -2
  271. package/dist/features/stores/components/StoreScopedSearch.d.ts +16 -0
  272. package/dist/features/stores/components/StoreScopedSearch.js +27 -0
  273. package/dist/features/stores/components/StoreSettingsGuideView.d.ts +1 -2
  274. package/dist/features/stores/server.d.ts +0 -1
  275. package/dist/features/stores/server.js +0 -1
  276. package/dist/features/support/schemas/firestore.d.ts +22 -1
  277. package/dist/features/support/schemas/firestore.js +10 -0
  278. package/dist/features/wishlist/server.d.ts +0 -1
  279. package/dist/features/wishlist/server.js +0 -1
  280. package/dist/index.d.ts +9 -27
  281. package/dist/index.js +14 -50
  282. package/dist/next/routing/route-map.d.ts +8 -0
  283. package/dist/next/routing/route-map.js +4 -0
  284. package/dist/react/hooks/useListingTypeFlags.d.ts +32 -0
  285. package/dist/react/hooks/useListingTypeFlags.js +19 -0
  286. package/dist/security/index.d.ts +1 -1
  287. package/dist/security/index.js +1 -1
  288. package/dist/security/pii-redact.d.ts +8 -0
  289. package/dist/security/pii-redact.js +10 -1
  290. package/dist/seed/grouped-listings-seed-data.d.ts +4 -0
  291. package/dist/seed/products-preorders-seed-data.d.ts +4 -0
  292. package/dist/seed/products-prize-draws-seed-data.js +3 -0
  293. package/dist/seed/site-settings-seed-data.js +5 -5
  294. package/dist/seed/sub-listings-seed-data.d.ts +4 -0
  295. package/dist/server.d.ts +2 -0
  296. package/dist/server.js +4 -0
  297. package/dist/styles.css +54 -395
  298. package/dist/tailwind-utilities.css +1 -1
  299. package/dist/ui/components/BaseListingCard.d.ts +10 -0
  300. package/dist/ui/components/BaseListingCard.js +17 -1
  301. package/dist/ui/components/DetailPageGallery.d.ts +32 -0
  302. package/dist/ui/components/DetailPageGallery.js +47 -0
  303. package/dist/ui/components/DetailPageHero.d.ts +30 -0
  304. package/dist/ui/components/DetailPageHero.js +22 -0
  305. package/dist/ui/components/DetailPageTabs.d.ts +34 -0
  306. package/dist/ui/components/DetailPageTabs.js +38 -0
  307. package/dist/ui/components/HorizontalScroller.style.css +15 -2
  308. package/dist/ui/components/ListingLayout.d.ts +11 -2
  309. package/dist/ui/components/ListingLayout.js +12 -6
  310. package/dist/ui/components/PaginatedSelect.d.ts +66 -0
  311. package/dist/ui/components/PaginatedSelect.js +201 -0
  312. package/dist/ui/components/PaginatedSelect.style.css +254 -0
  313. package/dist/ui/components/Semantic.d.ts +0 -4
  314. package/dist/ui/components/Semantic.js +0 -2
  315. package/dist/ui/components/SettingsSection.d.ts +47 -0
  316. package/dist/ui/components/SettingsSection.js +38 -0
  317. package/dist/ui/components/index.style.css +1 -4
  318. package/dist/ui/index.d.ts +12 -14
  319. package/dist/ui/index.js +6 -7
  320. package/dist/utils/id-generators.d.ts +1 -18
  321. package/dist/utils/id-generators.js +51 -18
  322. package/dist/validation/schemas.d.ts +25 -0
  323. package/dist/validation/schemas.js +28 -0
  324. package/package.json +1 -1
@@ -1,38 +1,18 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import React, { useState, useCallback } from "react";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import React, { useState } from "react";
4
4
  import { useMutation, useQueryClient } from "@tanstack/react-query";
5
- import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
- import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
- import { BulkActionBar, Button, FilterChipGroup, Form, FormActions, Input, ListingToolbar, Modal, Pagination, ListingViewShell, RowActionMenu, Text as AppText, useToast, ListingFilterDrawer } from "../../../ui";
8
- import { AdminViewCards } from "./AdminViewCards";
5
+ import { Button, FilterChipGroup, Form, FormActions, Input, ListingLayout, Modal, RowActionMenu, Text as AppText, useToast, } from "../../../ui";
9
6
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
7
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
11
8
  import { ADMIN_USER_STATUS_TABS, ADMIN_USER_ROLE_TABS } from "../constants/filter-tabs";
12
- import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
9
+ import { toRecordArray, toRelativeDate, toStringValue, } from "../hooks/useAdminListingData";
10
+ import { DataListingView } from "./DataListingView";
13
11
  import { apiClient } from "../../../http";
14
- import { DataTable } from "./DataTable";
15
12
  import { AdminUserEditorView } from "./AdminUserEditorView";
16
- import { useBottomActions } from "../../layout";
17
- const PAGE_SIZE = 25;
18
- const FILTER_KEYS = ["status", "role"];
19
- const DEFAULT_SORT = "-createdAt";
20
- const SORT_OPTIONS = [
21
- { value: "-createdAt", label: "Newest" },
22
- { value: "createdAt", label: "Oldest" },
23
- { value: "displayName", label: "Name A–Z" },
24
- ];
25
- const STATUS_OPTIONS = ADMIN_USER_STATUS_TABS;
26
- const ROLE_OPTIONS = ADMIN_USER_ROLE_TABS;
27
13
  export function AdminUsersView({ children, ...props }) {
28
- const hasChildren = React.Children.count(children) > 0;
29
- const [view, setView] = useState("table");
30
14
  const toast = useToast();
31
15
  const queryClient = useQueryClient();
32
- const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
33
- const [searchInput, setSearchInput] = useState(table.get("q") || "");
34
- const [filterOpen, setFilterOpen] = useState(false);
35
- const [pendingFilters, setPendingFilters] = useState(() => Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
36
16
  const [drawerOpen, setDrawerOpen] = useState(false);
37
17
  const [selectedRow, setSelectedRow] = useState(null);
38
18
  const [banModalOpen, setBanModalOpen] = useState(false);
@@ -51,7 +31,9 @@ export function AdminUsersView({ children, ...props }) {
51
31
  setBanReason("");
52
32
  void queryClient.invalidateQueries({ queryKey: ["admin", "users", "listing"] });
53
33
  },
54
- onError: () => { toast.showToast("Failed to ban user.", "error"); },
34
+ onError: () => {
35
+ toast.showToast("Failed to ban user.", "error");
36
+ },
55
37
  });
56
38
  const unbanUser = useMutation({
57
39
  mutationFn: (uid) => apiClient.post(ADMIN_ENDPOINTS.USER_UNBAN(uid), {}),
@@ -59,51 +41,27 @@ export function AdminUsersView({ children, ...props }) {
59
41
  toast.showToast("Ban lifted.", "success");
60
42
  void queryClient.invalidateQueries({ queryKey: ["admin", "users", "listing"] });
61
43
  },
62
- onError: () => { toast.showToast("Failed to lift ban.", "error"); },
44
+ onError: () => {
45
+ toast.showToast("Failed to lift ban.", "error");
46
+ },
63
47
  });
64
- const openFilters = useCallback(() => {
65
- setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
66
- setFilterOpen(true);
67
- }, [table]);
68
- const applyFilters = useCallback(() => {
69
- const updates = { page: "1" };
70
- for (const k of FILTER_KEYS)
71
- updates[k] = pendingFilters[k] ?? "";
72
- table.setMany(updates);
73
- setFilterOpen(false);
74
- }, [pendingFilters, table]);
75
- const clearFilters = useCallback(() => {
76
- setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, ""])));
77
- }, []);
78
- const resetAll = useCallback(() => {
79
- const updates = { q: "", sort: "" };
80
- for (const k of FILTER_KEYS)
81
- updates[k] = "";
82
- table.setMany(updates);
83
- setSearchInput("");
84
- }, [table]);
85
- const commitSearch = useCallback(() => {
86
- table.set("q", searchInput.trim());
87
- }, [searchInput, table]);
88
- const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
89
- const hasActiveState = !!table.get("q") || table.get("sort") !== DEFAULT_SORT || activeFilterCount > 0;
90
- const filterParts = [];
91
- const statusRaw = table.get("status");
92
- if (statusRaw && statusRaw !== "All") {
93
- filterParts.push(statusRaw === "Active" ? "disabled==false" : "disabled==true");
48
+ if (React.Children.count(children) > 0) {
49
+ return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
94
50
  }
95
- const roleRaw = table.get("role");
96
- if (roleRaw && roleRaw !== "All")
97
- filterParts.push(`role==${roleRaw}`);
98
- const filters = filterParts.join(",") || undefined;
99
- const { rows, total, isLoading, errorMessage } = useAdminListingData({
51
+ const config = {
52
+ portal: "admin",
53
+ title: "Users",
54
+ searchPlaceholder: "Search users, email, or seller handles",
55
+ emptyLabel: "No users found",
56
+ filterKeys: ["status", "role"],
57
+ defaultSort: "-createdAt",
100
58
  queryKey: ["admin", "users", "listing"],
101
59
  endpoint: ADMIN_ENDPOINTS.USERS,
102
- page: table.getNumber("page", 1),
103
- pageSize: PAGE_SIZE,
104
- sorts: table.get("sort") || DEFAULT_SORT,
105
- filters,
106
- q: table.get("q") || undefined,
60
+ sortOptions: [
61
+ { value: "-createdAt", label: "Newest" },
62
+ { value: "createdAt", label: "Oldest" },
63
+ { value: "displayName", label: "Name A–Z" },
64
+ ],
107
65
  mapRows: (response) => toRecordArray(response.users).map((item, index) => {
108
66
  const isDisabled = Boolean(item.isDisabled ?? item.disabled);
109
67
  const isHardBanned = isDisabled && Boolean(item.hardBanReason);
@@ -134,38 +92,64 @@ export function AdminUsersView({ children, ...props }) {
134
92
  return response.total;
135
93
  return mappedRows.length;
136
94
  },
137
- });
138
- const currentPage = table.getNumber("page", 1);
139
- const totalPages = Math.ceil(total / PAGE_SIZE);
140
- const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
141
- if (hasChildren) {
142
- return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
143
- }
144
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search users, email, or seller handles", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
145
- { id: "manage", label: ACTIONS.ADMIN["manage-user"].label, variant: "primary", onClick: () => { setSelectedRow(rows.find(r => r.id === selection.selectedIds[0]) ?? null); setDrawerOpen(true); selection.clearSelection(); } },
146
- ] }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-error-surface px-4 py-3 text-sm text-error dark:border-red-900/60", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No users found", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next ? selection.setSelectedIds(rows.map(r => r.id)) : selection.clearSelection(), renderRowActions: (row) => {
147
- const ur = row;
148
- const isBanned = ur.status === "Hard banned";
149
- useBottomActions(selection.selectedCount > 0 ? { bulk: { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
150
- { id: "manage", label: ACTIONS.ADMIN["manage-user"].label, variant: "primary", onClick: () => { setSelectedRow(rows.find(r => r.id === selection.selectedIds[0]) ?? null); setDrawerOpen(true); selection.clearSelection(); } },
151
- ] } } : {});
152
- return (_jsx(RowActionMenu, { actions: [
153
- {
154
- label: ACTIONS.ADMIN["manage-user"].label,
155
- onClick: () => { setSelectedRow(ur); setDrawerOpen(true); },
156
- },
157
- {
158
- label: ACTIONS.ADMIN["ban-user"].label,
159
- onClick: () => { setBanTargetId(ur.id); setBanReason(""); setBanModalOpen(true); },
160
- disabled: isBanned,
161
- },
162
- {
163
- label: ACTIONS.ADMIN["unban-user"].label,
164
- onClick: () => unbanUser.mutate(ur.id),
165
- disabled: !isBanned || unbanUser.isPending,
166
- },
167
- ] }));
168
- } })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No users found", onRowClick: (row) => { setSelectedRow(row); setDrawerOpen(true); }, selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), _jsxs(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearFilters, activeCount: activeFilterCount, children: [_jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Role", tabs: ROLE_OPTIONS, value: pendingFilters.role ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, role: id })) })] })] }), _jsx(AdminUserEditorView, { open: drawerOpen, onClose: () => setDrawerOpen(false), userId: selectedRow?.id, displayName: selectedRow?.primary, currentRole: toStringValue(selectedRow?._raw?.role, "user"), currentEmailVerified: Boolean(selectedRow?._raw?.emailVerified), ownedStoreId: toStringValue(selectedRow?._raw?.storeId, "") || undefined, ownedStoreName: toStringValue(selectedRow?._raw?.storeName, "") || undefined, currentIsHardBanned: Boolean((selectedRow?._raw?.isDisabled ?? selectedRow?._raw?.disabled) &&
95
+ buildFilters: (f) => {
96
+ const parts = [];
97
+ if (f.status && f.status !== "All") {
98
+ parts.push(f.status === "Active" ? "disabled==false" : "disabled==true");
99
+ }
100
+ if (f.role && f.role !== "All")
101
+ parts.push(`role==${f.role}`);
102
+ return parts.join(",") || undefined;
103
+ },
104
+ onRowClick: (row) => {
105
+ setSelectedRow(row);
106
+ setDrawerOpen(true);
107
+ },
108
+ buildBulkActions: (selection) => [
109
+ {
110
+ id: "manage",
111
+ label: ACTIONS.ADMIN["manage-user"].label,
112
+ variant: "primary",
113
+ onClick: () => {
114
+ const id = selection.selectedIds[0];
115
+ const row = selection.rows.find((r) => r.id === id) ?? null;
116
+ if (row) {
117
+ setSelectedRow(row);
118
+ setDrawerOpen(true);
119
+ }
120
+ selection.clearSelection();
121
+ },
122
+ },
123
+ ],
124
+ renderRowActions: (row) => {
125
+ const isBanned = row.status === "Hard banned";
126
+ return (_jsx(RowActionMenu, { actions: [
127
+ {
128
+ label: ACTIONS.ADMIN["manage-user"].label,
129
+ onClick: () => {
130
+ setSelectedRow(row);
131
+ setDrawerOpen(true);
132
+ },
133
+ },
134
+ {
135
+ label: ACTIONS.ADMIN["ban-user"].label,
136
+ onClick: () => {
137
+ setBanTargetId(row.id);
138
+ setBanReason("");
139
+ setBanModalOpen(true);
140
+ },
141
+ disabled: isBanned,
142
+ },
143
+ {
144
+ label: ACTIONS.ADMIN["unban-user"].label,
145
+ onClick: () => unbanUser.mutate(row.id),
146
+ disabled: !isBanned || unbanUser.isPending,
147
+ },
148
+ ] }));
149
+ },
150
+ renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_USER_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Role", tabs: ADMIN_USER_ROLE_TABS, value: pendingFilters.role ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, role: id })) })] })),
151
+ };
152
+ return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(AdminUserEditorView, { open: drawerOpen, onClose: () => setDrawerOpen(false), userId: selectedRow?.id, displayName: selectedRow?.primary, currentRole: toStringValue(selectedRow?._raw?.role, "user"), currentEmailVerified: Boolean(selectedRow?._raw?.emailVerified), ownedStoreId: toStringValue(selectedRow?._raw?.storeId, "") || undefined, ownedStoreName: toStringValue(selectedRow?._raw?.storeName, "") || undefined, currentIsHardBanned: Boolean((selectedRow?._raw?.isDisabled ?? selectedRow?._raw?.disabled) &&
169
153
  selectedRow?._raw?.hardBanReason), currentHardBanReason: toStringValue(selectedRow?._raw?.hardBanReason, "") || undefined, currentSoftBans: Array.isArray(selectedRow?._raw?.softBans)
170
154
  ? selectedRow._raw.softBans.map((b) => ({
171
155
  action: toStringValue(b.action, ""),
@@ -180,5 +164,29 @@ export function AdminUsersView({ children, ...props }) {
180
164
  : null,
181
165
  bannedBy: toStringValue(b.bannedBy, ""),
182
166
  }))
183
- : undefined }), _jsxs(Modal, { isOpen: banModalOpen, onClose: () => { setBanModalOpen(false); setBanTargetId(null); setBanReason(""); }, title: ACTIONS.ADMIN["ban-user"].confirmation.title, children: [_jsx(AppText, { size: "sm", color: "muted", className: "mb-4", children: ACTIONS.ADMIN["ban-user"].confirmation.body }), _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); banUser.mutate(); }, children: [_jsx(Input, { label: "Reason", value: banReason, onChange: (e) => setBanReason(e.target.value), placeholder: "e.g. Repeated fraud, scam activity\u2026", required: true }), _jsxs(FormActions, { children: [_jsx(Button, { type: "button", variant: "secondary", onClick: () => { setBanModalOpen(false); setBanTargetId(null); setBanReason(""); }, children: "Cancel" }), _jsx(Button, { type: "submit", variant: "danger", isLoading: banUser.isPending, disabled: !banReason.trim() || banUser.isPending, children: ACTIONS.ADMIN["ban-user"].confirmation.confirmLabel })] })] })] })] }));
167
+ : undefined,
168
+ // ST-2 — extended profile fields
169
+ currentPhoneNumber: toStringValue(selectedRow?._raw?.phoneNumber, "") || undefined, currentBio: toStringValue(selectedRow?._raw?.publicProfile?.bio, "") || undefined, currentLocation: toStringValue(selectedRow?._raw?.publicProfile?.location, "") || undefined, currentWebsite: toStringValue(selectedRow?._raw?.publicProfile?.website, "") || undefined, currentSocialLinks: (() => {
170
+ const pp = selectedRow?._raw?.publicProfile;
171
+ const sl = pp?.socialLinks;
172
+ if (!sl)
173
+ return undefined;
174
+ return {
175
+ twitter: toStringValue(sl.twitter, "") || undefined,
176
+ instagram: toStringValue(sl.instagram, "") || undefined,
177
+ facebook: toStringValue(sl.facebook, "") || undefined,
178
+ linkedin: toStringValue(sl.linkedin, "") || undefined,
179
+ };
180
+ })() }), _jsxs(Modal, { isOpen: banModalOpen, onClose: () => {
181
+ setBanModalOpen(false);
182
+ setBanTargetId(null);
183
+ setBanReason("");
184
+ }, title: ACTIONS.ADMIN["ban-user"].confirmation.title, children: [_jsx(AppText, { size: "sm", color: "muted", className: "mb-4", children: ACTIONS.ADMIN["ban-user"].confirmation.body }), _jsxs(Form, { onSubmit: (e) => {
185
+ e.preventDefault();
186
+ banUser.mutate();
187
+ }, children: [_jsx(Input, { label: "Reason", value: banReason, onChange: (e) => setBanReason(e.target.value), placeholder: "e.g. Repeated fraud, scam activity\u2026", required: true }), _jsxs(FormActions, { children: [_jsx(Button, { type: "button", variant: "secondary", onClick: () => {
188
+ setBanModalOpen(false);
189
+ setBanTargetId(null);
190
+ setBanReason("");
191
+ }, children: "Cancel" }), _jsx(Button, { type: "submit", variant: "danger", isLoading: banUser.isPending, disabled: !banReason.trim() || banUser.isPending, children: ACTIONS.ADMIN["ban-user"].confirmation.confirmLabel })] })] })] })] }));
184
192
  }
@@ -1,4 +1,4 @@
1
- import type { AdminListingScaffoldRow } from "./AdminListingScaffold";
1
+ import type { AdminListingScaffoldRow } from "./DataListingView";
2
2
  interface AdminViewCardsProps {
3
3
  rows: AdminListingScaffoldRow[];
4
4
  view: "grid" | "list";
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Text } from "../../../ui";
3
+ import { Div, Grid, Row, Span, Stack, Text } from "../../../ui";
4
4
  const FLAG_BADGES = [
5
5
  { key: "featured", label: "Featured", color: "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300" },
6
6
  { key: "isPromoted", label: "Promoted", color: "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300" },
@@ -8,13 +8,13 @@ const FLAG_BADGES = [
8
8
  { key: "isSold", label: "Sold", color: "bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400" },
9
9
  ];
10
10
  function StatusBadge({ status }) {
11
- return (_jsx("span", { className: "inline-flex rounded-full bg-primary-50 px-2 py-0.5 text-xs font-medium text-primary-800 dark:bg-secondary-900/30 dark:text-secondary-300 truncate max-w-[120px]", children: status }));
11
+ return (_jsx(Span, { className: "inline-flex rounded-full bg-primary-50 px-2 py-0.5 text-xs font-medium text-primary-800 dark:bg-secondary-900/30 dark:text-secondary-300 truncate max-w-[120px]", children: status }));
12
12
  }
13
13
  function SkeletonCard({ view }) {
14
14
  if (view === "list") {
15
- return (_jsxs("div", { className: "flex items-center gap-3 px-4 py-3 animate-pulse", children: [_jsx("div", { className: "h-4 w-4 rounded bg-zinc-200 dark:bg-slate-700 shrink-0" }), _jsxs("div", { className: "flex-1 space-y-1.5", children: [_jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-2/3" }), _jsx("div", { className: "h-2.5 bg-zinc-200 dark:bg-slate-700 rounded w-1/3" })] }), _jsx("div", { className: "h-5 w-16 bg-zinc-200 dark:bg-slate-700 rounded-full shrink-0" }), _jsx("div", { className: "h-3 w-20 bg-zinc-200 dark:bg-slate-700 rounded shrink-0" })] }));
15
+ return (_jsxs(Row, { gap: "sm", className: "px-4 py-3 animate-pulse", children: [_jsx(Div, { className: "h-4 w-4 rounded bg-zinc-200 dark:bg-slate-700 shrink-0" }), _jsxs(Stack, { gap: "xs", className: "flex-1", children: [_jsx(Div, { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-2/3" }), _jsx(Div, { className: "h-2.5 bg-zinc-200 dark:bg-slate-700 rounded w-1/3" })] }), _jsx(Div, { className: "h-5 w-16 bg-zinc-200 dark:bg-slate-700 rounded-full shrink-0" }), _jsx(Div, { className: "h-3 w-20 bg-zinc-200 dark:bg-slate-700 rounded shrink-0" })] }));
16
16
  }
17
- return (_jsx("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: _jsxs("div", { className: "p-4 space-y-2.5", children: [_jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" }), _jsx("div", { className: "h-5 bg-zinc-200 dark:bg-slate-700 rounded-full w-20 mt-1" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/3" })] }) }));
17
+ return (_jsx(Div, { rounded: "xl", border: "subtle", className: "overflow-hidden animate-pulse", children: _jsxs(Stack, { gap: "xs", padding: "md", children: [_jsx(Div, { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx(Div, { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" }), _jsx(Div, { className: "h-5 bg-zinc-200 dark:bg-slate-700 rounded-full w-20 mt-1" }), _jsx(Div, { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/3" })] }) }));
18
18
  }
19
19
  function AdminCardItem({ row, view, selected, onToggleSelect, onRowClick, }) {
20
20
  const flags = FLAG_BADGES.filter(({ key }) => Boolean(row[key]));
@@ -24,31 +24,31 @@ function AdminCardItem({ row, view, selected, onToggleSelect, onRowClick, }) {
24
24
  onRowClick?.(row);
25
25
  };
26
26
  if (view === "list") {
27
- return (_jsxs("div", { className: [
28
- "flex items-center gap-3 px-4 py-3 cursor-pointer transition-colors hover:bg-zinc-50 dark:hover:bg-slate-800/50",
27
+ return (_jsxs(Row, { gap: "sm", className: [
28
+ "px-4 py-3 cursor-pointer transition-colors hover:bg-zinc-50 dark:hover:bg-slate-800/50",
29
29
  selected ? "bg-primary-50/40 dark:bg-primary-900/10" : "",
30
- ].filter(Boolean).join(" "), onClick: handleClick, role: onRowClick ? "button" : undefined, children: [onToggleSelect && (_jsx("div", { "data-checkbox": true, className: "shrink-0", onClick: (e) => { e.stopPropagation(); onToggleSelect(row.id); }, children: _jsx("input", { type: "checkbox", checked: selected, onChange: () => onToggleSelect(row.id), className: "h-4 w-4 rounded border-zinc-300 text-primary accent-primary cursor-pointer", "aria-label": `Select ${row.primary}` }) })), _jsxs("div", { className: "flex-1 min-w-0 space-y-0.5", children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100 truncate", children: row.primary }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 truncate", children: row.secondary })] }), flags.length > 0 && (_jsx("div", { className: "hidden sm:flex items-center gap-1 shrink-0", children: flags.map(({ key, label, color }) => (_jsx("span", { className: `inline-flex rounded-full px-1.5 py-0.5 text-[10px] font-medium ${color}`, children: label }, key))) })), _jsx(StatusBadge, { status: row.status }), _jsx("span", { className: "hidden sm:block text-xs text-zinc-400 dark:text-zinc-400 shrink-0 w-24 text-right", children: row.updatedAt })] }));
30
+ ].filter(Boolean).join(" "), onClick: handleClick, role: onRowClick ? "button" : undefined, children: [onToggleSelect && (_jsx(Div, { "data-checkbox": true, className: "shrink-0", onClick: (e) => { e.stopPropagation(); onToggleSelect(row.id); }, children: _jsx("input", { type: "checkbox", checked: selected, onChange: () => onToggleSelect(row.id), className: "h-4 w-4 rounded border-zinc-300 text-primary accent-primary cursor-pointer", "aria-label": `Select ${row.primary}` }) })), _jsxs(Stack, { gap: "none", className: "flex-1 min-w-0", children: [_jsx(Text, { size: "sm", weight: "semibold", className: "truncate text-zinc-900 dark:text-zinc-100", children: row.primary }), _jsx(Text, { size: "xs", color: "muted", className: "truncate", children: row.secondary })] }), flags.length > 0 && (_jsx(Row, { gap: "xs", className: "hidden sm:flex shrink-0", children: flags.map(({ key, label, color }) => (_jsx(Span, { className: `inline-flex rounded-full px-1.5 py-0.5 text-[10px] font-medium ${color}`, children: label }, key))) })), _jsx(StatusBadge, { status: row.status }), _jsx(Span, { size: "xs", color: "muted", className: "hidden sm:block shrink-0 w-24 text-right", children: row.updatedAt })] }));
31
31
  }
32
- return (_jsxs("div", { className: [
33
- "rounded-xl border overflow-hidden cursor-pointer transition-all hover:shadow-md hover:-translate-y-0.5",
32
+ return (_jsxs(Div, { rounded: "xl", className: [
33
+ "border overflow-hidden cursor-pointer transition-all hover:shadow-md hover:-translate-y-0.5",
34
34
  selected
35
35
  ? "border-primary ring-1 ring-primary/20 bg-primary-50/30 dark:bg-primary-900/10"
36
36
  : "border-zinc-100 dark:border-slate-700 bg-white dark:bg-slate-900",
37
- ].filter(Boolean).join(" "), onClick: handleClick, role: onRowClick ? "button" : undefined, children: [onToggleSelect && (_jsxs("div", { "data-checkbox": true, className: "flex items-center gap-2 px-3 pt-3", onClick: (e) => { e.stopPropagation(); onToggleSelect(row.id); }, children: [_jsx("input", { type: "checkbox", checked: selected, onChange: () => onToggleSelect(row.id), className: "h-4 w-4 rounded border-zinc-300 text-primary accent-primary cursor-pointer", "aria-label": `Select ${row.primary}` }), flags.length > 0 && (_jsx("div", { className: "flex items-center gap-1 flex-wrap", children: flags.map(({ key, label, color }) => (_jsx("span", { className: `inline-flex rounded-full px-1.5 py-0.5 text-[10px] font-medium ${color}`, children: label }, key))) }))] })), _jsxs("div", { className: "p-3.5 space-y-2", children: [_jsxs("div", { className: "space-y-0.5", children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100 line-clamp-2 leading-snug", children: row.primary }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 truncate", children: row.secondary })] }), _jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx(StatusBadge, { status: row.status }), _jsx("span", { className: "text-[11px] text-zinc-400 dark:text-zinc-400 shrink-0", children: row.updatedAt })] })] })] }));
37
+ ].filter(Boolean).join(" "), onClick: handleClick, role: onRowClick ? "button" : undefined, children: [onToggleSelect && (_jsxs(Row, { gap: "xs", "data-checkbox": true, className: "px-3 pt-3", onClick: (e) => { e.stopPropagation(); onToggleSelect(row.id); }, children: [_jsx("input", { type: "checkbox", checked: selected, onChange: () => onToggleSelect(row.id), className: "h-4 w-4 rounded border-zinc-300 text-primary accent-primary cursor-pointer", "aria-label": `Select ${row.primary}` }), flags.length > 0 && (_jsx(Row, { gap: "xs", wrap: true, children: flags.map(({ key, label, color }) => (_jsx(Span, { className: `inline-flex rounded-full px-1.5 py-0.5 text-[10px] font-medium ${color}`, children: label }, key))) }))] })), _jsxs(Stack, { gap: "xs", className: "p-3.5", children: [_jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "sm", weight: "semibold", className: "line-clamp-2 leading-snug text-zinc-900 dark:text-zinc-100", children: row.primary }), _jsx(Text, { size: "xs", color: "muted", className: "truncate", children: row.secondary })] }), _jsxs(Row, { justify: "between", gap: "xs", children: [_jsx(StatusBadge, { status: row.status }), _jsx(Span, { color: "muted", className: "text-[11px] shrink-0", children: row.updatedAt })] })] })] }));
38
38
  }
39
39
  export function AdminViewCards({ rows, view, isLoading, emptyLabel = "No items found", onRowClick, selectedIdSet, onToggleSelect, }) {
40
40
  if (isLoading) {
41
41
  const count = view === "grid" ? 12 : 8;
42
42
  if (view === "list") {
43
- return (_jsx("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden divide-y divide-zinc-100 dark:divide-slate-700", children: Array.from({ length: count }).map((_, i) => _jsx(SkeletonCard, { view: "list" }, i)) }));
43
+ return (_jsx(Div, { rounded: "xl", border: "subtle", className: "overflow-hidden divide-y divide-zinc-100 dark:divide-slate-700", children: Array.from({ length: count }).map((_, i) => _jsx(SkeletonCard, { view: "list" }, i)) }));
44
44
  }
45
- return (_jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4", children: Array.from({ length: count }).map((_, i) => _jsx(SkeletonCard, { view: "grid" }, i)) }));
45
+ return (_jsx(Grid, { gap: "md", className: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-4", children: Array.from({ length: count }).map((_, i) => _jsx(SkeletonCard, { view: "grid" }, i)) }));
46
46
  }
47
47
  if (rows.length === 0) {
48
- return (_jsx(Text, { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: emptyLabel }));
48
+ return (_jsx(Text, { size: "sm", color: "muted", className: "py-12 text-center", children: emptyLabel }));
49
49
  }
50
50
  if (view === "list") {
51
- return (_jsx("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden divide-y divide-zinc-100 dark:divide-slate-700", children: rows.map((row) => (_jsx(AdminCardItem, { row: row, view: "list", selected: selectedIdSet?.has(row.id) ?? false, onToggleSelect: onToggleSelect, onRowClick: onRowClick }, row.id))) }));
51
+ return (_jsx(Div, { rounded: "xl", border: "subtle", className: "overflow-hidden divide-y divide-zinc-100 dark:divide-slate-700", children: rows.map((row) => (_jsx(AdminCardItem, { row: row, view: "list", selected: selectedIdSet?.has(row.id) ?? false, onToggleSelect: onToggleSelect, onRowClick: onRowClick }, row.id))) }));
52
52
  }
53
- return (_jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4", children: rows.map((row) => (_jsx(AdminCardItem, { row: row, view: "grid", selected: selectedIdSet?.has(row.id) ?? false, onToggleSelect: onToggleSelect, onRowClick: onRowClick }, row.id))) }));
53
+ return (_jsx(Grid, { gap: "md", className: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-4", children: rows.map((row) => (_jsx(AdminCardItem, { row: row, view: "grid", selected: selectedIdSet?.has(row.id) ?? false, onToggleSelect: onToggleSelect, onRowClick: onRowClick }, row.id))) }));
54
54
  }
@@ -1,4 +1,3 @@
1
- import type { ListingViewShellProps } from "../../../ui";
2
- export interface AdminWishlistsViewProps extends ListingViewShellProps {
3
- }
1
+ import type { ListingLayoutProps } from "../../../ui";
2
+ export type AdminWishlistsViewProps = ListingLayoutProps;
4
3
  export declare function AdminWishlistsView({ children, ...props }: AdminWishlistsViewProps): import("react/jsx-runtime").JSX.Element;
@@ -1,67 +1,41 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import React, { useState, useCallback } from "react";
4
- import { useUrlTable } from "../../../react/hooks/useUrlTable";
5
- import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
6
- import { ListingToolbar, Pagination, ListingViewShell } from "../../../ui";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import React from "react";
4
+ import { ListingLayout } from "../../../ui";
7
5
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
- import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
9
- import { DataTable } from "./DataTable";
10
- const PAGE_SIZE = 25;
11
- const FILTER_KEYS = [];
12
- const DEFAULT_SORT = "-updatedAt";
13
- const SORT_OPTIONS = [
14
- { value: "-updatedAt", label: "Recently updated" },
15
- { value: "-itemCount", label: "Largest first" },
16
- ];
6
+ import { toRecordArray, toRelativeDate, toStringValue, } from "../hooks/useAdminListingData";
7
+ import { DataListingView } from "./DataListingView";
8
+ const ADMIN_WISHLISTS_CONFIG = {
9
+ portal: "admin",
10
+ title: "Wishlists",
11
+ searchPlaceholder: "Search by user ID",
12
+ emptyLabel: "No user wishlists found",
13
+ filterKeys: [],
14
+ defaultSort: "-updatedAt",
15
+ queryKey: ["admin", "wishlists", "listing"],
16
+ endpoint: ADMIN_ENDPOINTS.ADMIN_WISHLISTS,
17
+ sortOptions: [
18
+ { value: "-updatedAt", label: "Recently updated" },
19
+ { value: "-itemCount", label: "Largest first" },
20
+ ],
21
+ mapRows: (response) => toRecordArray(response.items).map((item, index) => {
22
+ const itemCount = typeof item.itemCount === "number" ? item.itemCount : 0;
23
+ const limit = typeof item.limit === "number" ? item.limit : 20;
24
+ const isFull = item.isFull === true;
25
+ return {
26
+ id: toStringValue(item.id, `wish-${index}`),
27
+ primary: toStringValue(item.userId, "Unknown user"),
28
+ secondary: `${itemCount} item${itemCount === 1 ? "" : "s"} of ${limit}`,
29
+ status: isFull ? "Full" : itemCount >= limit - 2 ? "Near cap" : "OK",
30
+ updatedAt: toRelativeDate(item.updatedAt),
31
+ };
32
+ }),
33
+ getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
34
+ buildFilters: () => undefined,
35
+ };
17
36
  export function AdminWishlistsView({ children, ...props }) {
18
- const hasChildren = React.Children.count(children) > 0;
19
- const [view, setView] = useState("table");
20
- const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
21
- const [searchInput, setSearchInput] = useState(table.get("q") || "");
22
- const [selectedIds, setSelectedIds] = useState(new Set());
23
- const toggleSelect = (id, next) => setSelectedIds((prev) => {
24
- const s = new Set(prev);
25
- if (next)
26
- s.add(id);
27
- else
28
- s.delete(id);
29
- return s;
30
- });
31
- const resetAll = useCallback(() => {
32
- table.setMany({ q: "", sort: "" });
33
- setSearchInput("");
34
- }, [table]);
35
- const commitSearch = useCallback(() => {
36
- table.set("q", searchInput.trim());
37
- }, [searchInput, table]);
38
- const hasActiveState = !!table.get("q") || table.get("sort") !== DEFAULT_SORT;
39
- const { rows, total, isLoading, errorMessage } = useAdminListingData({
40
- queryKey: ["admin", "wishlists", "listing"],
41
- endpoint: ADMIN_ENDPOINTS.ADMIN_WISHLISTS,
42
- page: table.getNumber("page", 1),
43
- pageSize: PAGE_SIZE,
44
- sorts: table.get("sort") || DEFAULT_SORT,
45
- q: table.get("q") || undefined,
46
- mapRows: (response) => toRecordArray(response.items).map((item, index) => {
47
- const itemCount = typeof item.itemCount === "number" ? item.itemCount : 0;
48
- const limit = typeof item.limit === "number" ? item.limit : 20;
49
- const isFull = item.isFull === true;
50
- return {
51
- id: toStringValue(item.id, `wish-${index}`),
52
- primary: toStringValue(item.userId, "Unknown user"),
53
- secondary: `${itemCount} item${itemCount === 1 ? "" : "s"} of ${limit}`,
54
- status: isFull ? "Full" : itemCount >= limit - 2 ? "Near cap" : "OK",
55
- updatedAt: toRelativeDate(item.updatedAt),
56
- };
57
- }),
58
- getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
59
- });
60
- const currentPage = table.getNumber("page", 1);
61
- const totalPages = Math.ceil(total / PAGE_SIZE);
62
- const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
63
- if (hasChildren) {
64
- return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
37
+ if (React.Children.count(children) > 0) {
38
+ return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
65
39
  }
66
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search by user ID", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-error-surface px-4 py-3 text-sm text-error dark:border-red-900/60", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No user wishlists found", selectedIds: selectedIds, onToggleSelect: toggleSelect, onToggleSelectAll: (next) => setSelectedIds(next ? new Set(rows.map((r) => r.id)) : new Set()) })] })] }));
40
+ return _jsx(DataListingView, { config: ADMIN_WISHLISTS_CONFIG });
67
41
  }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * DataListingView — config-driven listing scaffold for admin + seller dashboards.
3
+ *
4
+ * Replaces ~30 hand-wired admin/seller view components (AdminBrandsView, AdminCategoriesView,
5
+ * SellerCouponsView, etc.) by accepting a single `config` object describing:
6
+ * - data layer (endpoint, queryKey, mapRows, buildFilters, sortOptions)
7
+ * - presentation (title, subtitle, columns, searchPlaceholder, emptyLabel)
8
+ * - actions (bulkActions, primaryAction, renderRowActions)
9
+ * - filter panel (renderFilterPanel)
10
+ * - editor side-drawer (renderEditor)
11
+ *
12
+ * Composes: useAdminListing (data + state) + ListingToolbar + BulkActionBar + Pagination
13
+ * + DataTable | AdminViewCards + ListingFilterDrawer + SideDrawer
14
+ * + useBottomActions (mobile bulk bar)
15
+ *
16
+ * Usage:
17
+ * const ADMIN_BRANDS_CONFIG: ListingViewConfig<BrandsResponse, BrandRow> = { ... };
18
+ * <DataListingView config={ADMIN_BRANDS_CONFIG} />
19
+ *
20
+ * Created 2026-05-23 as part of W6-12 (plan: read-this-files-and-modular-storm.md).
21
+ */
22
+ import React from "react";
23
+ import type { BulkActionItem } from "../../../ui";
24
+ import type { AdminListingConfig } from "../hooks/useAdminListing";
25
+ import type { AdminTableColumn } from "../types";
26
+ /**
27
+ * Generic admin data row shape used by DataListingView and AdminViewCards.
28
+ * Moved here from AdminListingScaffold (which is now deleted) so views that
29
+ * need the base row type can import from this module instead.
30
+ */
31
+ export interface AdminListingScaffoldRow {
32
+ id: string;
33
+ primary: string;
34
+ secondary: string;
35
+ status: string;
36
+ updatedAt: string;
37
+ featured?: boolean;
38
+ isPromoted?: boolean;
39
+ isOnSale?: boolean;
40
+ isSold?: boolean;
41
+ }
42
+ export interface ListingSortOption {
43
+ value: string;
44
+ label: string;
45
+ }
46
+ /**
47
+ * Render context passed to filter panel + editor render-props.
48
+ * Mirrors the shape returned by `useAdminListing`.
49
+ */
50
+ export interface ListingPanelContext {
51
+ pendingFilters: Record<string, string>;
52
+ setPendingFilters: React.Dispatch<React.SetStateAction<Record<string, string>>>;
53
+ applyFilters: () => void;
54
+ clearFilters: () => void;
55
+ }
56
+ export interface ListingEditorContext {
57
+ isCreate: boolean;
58
+ editId: string | null;
59
+ closePanel: () => void;
60
+ }
61
+ export interface ListingSelectionContext<TRow extends {
62
+ id: string;
63
+ }> {
64
+ selectedIds: string[];
65
+ selectedCount: number;
66
+ clearSelection: () => void;
67
+ toggleSelect: (id: string) => void;
68
+ rows: TRow[];
69
+ openEditPanel: (id: string) => void;
70
+ }
71
+ export interface ListingViewConfig<TResponse, TRow extends {
72
+ id: string;
73
+ }> extends AdminListingConfig<TResponse, TRow> {
74
+ /** Portal context — currently admin/seller share the same chrome. */
75
+ portal: "admin" | "seller";
76
+ title: string;
77
+ subtitle?: string;
78
+ searchPlaceholder: string;
79
+ emptyLabel?: string;
80
+ sortOptions: ListingSortOption[];
81
+ columns?: AdminTableColumn<TRow>[];
82
+ /** Optional alt-view (grid/list) renderer. When omitted, AdminViewCards is used. */
83
+ renderCards?: (rows: TRow[], view: "grid" | "list", selection: ListingSelectionContext<TRow>, isLoading: boolean) => React.ReactNode;
84
+ primaryAction?: {
85
+ label: string;
86
+ onClick: (panel: {
87
+ openCreatePanel: () => void;
88
+ }) => void;
89
+ icon?: React.ReactNode;
90
+ };
91
+ /** Extra arbitrary content in the toolbar (e.g. export buttons). Rendered alongside primaryAction. */
92
+ toolbarExtra?: React.ReactNode;
93
+ /** Slot rendered between the toolbar and the table content (e.g. URL-driven scope tabs). */
94
+ renderAboveContent?: () => React.ReactNode;
95
+ /** Pill toggles displayed alongside the search/sort/filter row in ListingToolbar. */
96
+ toggles?: Array<{
97
+ label: string;
98
+ active: boolean;
99
+ onChange: (next: boolean) => void;
100
+ }>;
101
+ renderRowActions?: (row: TRow) => React.ReactNode;
102
+ buildBulkActions?: (selection: ListingSelectionContext<TRow>) => BulkActionItem[];
103
+ renderFilterPanel?: (ctx: ListingPanelContext) => React.ReactNode;
104
+ renderEditor?: (ctx: ListingEditorContext) => React.ReactNode;
105
+ /** Drawer title resolver — defaults to "Add X" / "Edit X" using `title`. */
106
+ resolveEditorTitle?: (ctx: ListingEditorContext) => string;
107
+ onRowClick?: (row: TRow, panel: {
108
+ openEditPanel: (id: string) => void;
109
+ }) => void;
110
+ /** When set, the table renders rows as anchor links to this href. */
111
+ getRowHref?: (row: TRow) => string;
112
+ /** Hide the table toggle in the toolbar; views with no table column set should use this. */
113
+ hideTableView?: boolean;
114
+ /** Initial view mode (defaults to "table" when not hidden, "grid" when hidden). */
115
+ initialView?: "grid" | "list" | "table";
116
+ className?: string;
117
+ }
118
+ export declare function DataListingView<TResponse, TRow extends {
119
+ id: string;
120
+ }>({ config, }: {
121
+ config: ListingViewConfig<TResponse, TRow>;
122
+ }): import("react/jsx-runtime").JSX.Element;