@mohasinac/appkit 2.3.1 → 2.3.2

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 (253) hide show
  1. package/dist/client.d.ts +12 -8
  2. package/dist/client.js +7 -4
  3. package/dist/constants/api-endpoints.d.ts +4 -0
  4. package/dist/constants/api-endpoints.js +2 -0
  5. package/dist/core/contact-submissions.repository.d.ts +32 -0
  6. package/dist/core/contact-submissions.repository.js +49 -0
  7. package/dist/core/index.d.ts +2 -0
  8. package/dist/core/index.js +1 -0
  9. package/dist/features/about/components/HowPayoutsWorkView.js +1 -1
  10. package/dist/features/account/components/AddressFilters.d.ts +5 -0
  11. package/dist/features/account/components/AddressFilters.js +20 -0
  12. package/dist/features/account/components/AddressesIndexListing.d.ts +6 -0
  13. package/dist/features/account/components/AddressesIndexListing.js +51 -0
  14. package/dist/features/account/components/UserSidebar.d.ts +7 -3
  15. package/dist/features/account/components/UserSidebar.js +55 -7
  16. package/dist/features/account/hooks/useAddresses.d.ts +7 -0
  17. package/dist/features/account/hooks/useAddresses.js +12 -1
  18. package/dist/features/admin/actions/admin-read-actions.d.ts +12 -0
  19. package/dist/features/admin/actions/admin-read-actions.js +18 -0
  20. package/dist/features/admin/components/AdminBlogView.js +26 -2
  21. package/dist/features/admin/components/AdminCarouselView.js +18 -2
  22. package/dist/features/admin/components/AdminCategoriesView.js +27 -2
  23. package/dist/features/admin/components/AdminContactView.d.ts +4 -0
  24. package/dist/features/admin/components/AdminContactView.js +50 -0
  25. package/dist/features/admin/components/AdminFaqsView.js +19 -2
  26. package/dist/features/admin/components/AdminListingScaffold.d.ts +11 -2
  27. package/dist/features/admin/components/AdminListingScaffold.js +14 -3
  28. package/dist/features/admin/components/AdminNewsletterView.d.ts +4 -0
  29. package/dist/features/admin/components/AdminNewsletterView.js +50 -0
  30. package/dist/features/admin/components/AdminProductsView.js +30 -2
  31. package/dist/features/admin/components/AdminReviewsView.js +26 -2
  32. package/dist/features/admin/components/AdminStoresView.js +17 -2
  33. package/dist/features/admin/components/AdminUsersView.js +27 -2
  34. package/dist/features/admin/components/DataTable.d.ts +2 -1
  35. package/dist/features/admin/components/DataTable.js +18 -4
  36. package/dist/features/admin/components/index.d.ts +6 -0
  37. package/dist/features/admin/components/index.js +3 -0
  38. package/dist/features/admin/hooks/useAdminListingData.d.ts +3 -1
  39. package/dist/features/admin/hooks/useAdminListingData.js +12 -7
  40. package/dist/features/admin/server.d.ts +3 -0
  41. package/dist/features/admin/server.js +2 -0
  42. package/dist/features/auctions/components/AuctionDetailPageView.js +93 -47
  43. package/dist/features/auctions/components/AuctionFilters.d.ts +8 -0
  44. package/dist/features/auctions/components/AuctionFilters.js +12 -0
  45. package/dist/features/auctions/components/AuctionsListView.d.ts +6 -1
  46. package/dist/features/auctions/components/AuctionsListView.js +37 -5
  47. package/dist/features/auctions/schemas/index.d.ts +4 -4
  48. package/dist/features/blog/components/BlogFeaturedCard.d.ts +1 -7
  49. package/dist/features/blog/components/BlogFeaturedCard.js +4 -5
  50. package/dist/features/blog/components/BlogFilters.js +2 -1
  51. package/dist/features/blog/components/BlogIndexListing.js +14 -9
  52. package/dist/features/blog/components/BlogIndexPageView.d.ts +6 -1
  53. package/dist/features/blog/components/BlogIndexPageView.js +10 -2
  54. package/dist/features/blog/components/BlogListView.d.ts +2 -1
  55. package/dist/features/blog/components/BlogListView.js +10 -3
  56. package/dist/features/categories/components/CategoriesIndexListing.d.ts +1 -1
  57. package/dist/features/categories/components/CategoriesIndexListing.js +41 -38
  58. package/dist/features/categories/components/CategoriesIndexPageView.d.ts +6 -1
  59. package/dist/features/categories/components/CategoriesIndexPageView.js +41 -2
  60. package/dist/features/categories/components/CategoryDetailPageView.js +13 -6
  61. package/dist/features/categories/components/CategoryDetailTabs.d.ts +5 -0
  62. package/dist/features/categories/components/CategoryDetailTabs.js +17 -0
  63. package/dist/features/categories/components/CategoryFilters.js +2 -1
  64. package/dist/features/categories/components/CategoryGrid.d.ts +2 -1
  65. package/dist/features/categories/components/CategoryGrid.js +8 -6
  66. package/dist/features/categories/components/CategoryProductsListing.js +22 -11
  67. package/dist/features/categories/components/index.d.ts +2 -0
  68. package/dist/features/categories/components/index.js +1 -0
  69. package/dist/features/categories/hooks/useCategories.d.ts +20 -0
  70. package/dist/features/categories/hooks/useCategories.js +50 -1
  71. package/dist/features/categories/hooks/useCategoryTree.d.ts +17 -0
  72. package/dist/features/categories/hooks/useCategoryTree.js +65 -0
  73. package/dist/features/events/components/AdminEventEditorView.d.ts +6 -0
  74. package/dist/features/events/components/AdminEventEditorView.js +203 -0
  75. package/dist/features/events/components/AdminEventsView.js +28 -2
  76. package/dist/features/events/components/EventCard.js +4 -2
  77. package/dist/features/events/components/EventFilters.js +2 -1
  78. package/dist/features/events/components/EventsIndexListing.js +40 -10
  79. package/dist/features/events/components/EventsListPageView.d.ts +6 -1
  80. package/dist/features/events/components/EventsListPageView.js +40 -7
  81. package/dist/features/events/components/index.d.ts +2 -0
  82. package/dist/features/events/components/index.js +1 -0
  83. package/dist/features/events/hooks/useEvents.js +2 -0
  84. package/dist/features/events/types/index.d.ts +1 -0
  85. package/dist/features/homepage/components/BlogArticlesSection.js +1 -1
  86. package/dist/features/homepage/components/EventsSection.js +1 -1
  87. package/dist/features/homepage/components/FeaturedAuctionsSection.js +1 -1
  88. package/dist/features/homepage/components/FeaturedPreOrdersSection.js +1 -1
  89. package/dist/features/homepage/components/FeaturedProductsSection.js +1 -1
  90. package/dist/features/homepage/components/FeaturedStoresSection.js +1 -1
  91. package/dist/features/homepage/components/HeroCarousel.js +1 -1
  92. package/dist/features/homepage/components/MarketplaceHomepageView.js +27 -17
  93. package/dist/features/homepage/components/SectionCarousel.js +4 -4
  94. package/dist/features/homepage/components/ShopByCategorySection.js +2 -2
  95. package/dist/features/homepage/schemas/firestore.d.ts +1 -1
  96. package/dist/features/homepage/schemas/firestore.js +2 -1
  97. package/dist/features/layout/AppLayoutShell.d.ts +6 -2
  98. package/dist/features/layout/AppLayoutShell.js +7 -3
  99. package/dist/features/pre-orders/components/MarketplacePreorderCard.d.ts +3 -1
  100. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +6 -2
  101. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +80 -12
  102. package/dist/features/pre-orders/components/PreOrderFilters.d.ts +8 -0
  103. package/dist/features/pre-orders/components/PreOrderFilters.js +21 -0
  104. package/dist/features/pre-orders/components/PreOrdersIndexListing.d.ts +2 -1
  105. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +69 -10
  106. package/dist/features/pre-orders/components/PreOrdersListView.d.ts +6 -1
  107. package/dist/features/pre-orders/components/PreOrdersListView.js +26 -7
  108. package/dist/features/pre-orders/components/index.d.ts +2 -0
  109. package/dist/features/pre-orders/components/index.js +1 -0
  110. package/dist/features/products/components/AuctionsIndexListing.d.ts +2 -1
  111. package/dist/features/products/components/AuctionsIndexListing.js +61 -9
  112. package/dist/features/products/components/InteractiveProductCard.d.ts +2 -4
  113. package/dist/features/products/components/InteractiveProductCard.js +2 -2
  114. package/dist/features/products/components/ProductDetailPageView.d.ts +1 -1
  115. package/dist/features/products/components/ProductDetailPageView.js +116 -25
  116. package/dist/features/products/components/ProductFilters.d.ts +6 -11
  117. package/dist/features/products/components/ProductFilters.js +5 -3
  118. package/dist/features/products/components/ProductGrid.d.ts +8 -2
  119. package/dist/features/products/components/ProductGrid.js +20 -5
  120. package/dist/features/products/components/ProductTabsShell.d.ts +3 -11
  121. package/dist/features/products/components/ProductTabsShell.js +14 -14
  122. package/dist/features/products/components/ProductsIndexListing.js +73 -9
  123. package/dist/features/products/components/ProductsIndexPageView.d.ts +6 -1
  124. package/dist/features/products/components/ProductsIndexPageView.js +39 -6
  125. package/dist/features/products/components/RelatedProductsCarousel.d.ts +7 -0
  126. package/dist/features/products/components/RelatedProductsCarousel.js +11 -0
  127. package/dist/features/products/components/index.d.ts +1 -0
  128. package/dist/features/products/components/index.js +1 -0
  129. package/dist/features/products/hooks/useProducts.js +16 -0
  130. package/dist/features/products/repository/products.repository.d.ts +8 -0
  131. package/dist/features/products/repository/products.repository.js +2 -0
  132. package/dist/features/products/schemas/index.d.ts +8 -8
  133. package/dist/features/products/types/index.d.ts +12 -0
  134. package/dist/features/promotions/components/CouponsIndexListing.d.ts +9 -0
  135. package/dist/features/promotions/components/CouponsIndexListing.js +86 -0
  136. package/dist/features/promotions/components/PromotionsView.d.ts +11 -5
  137. package/dist/features/promotions/components/PromotionsView.js +6 -1
  138. package/dist/features/promotions/components/index.d.ts +4 -2
  139. package/dist/features/promotions/components/index.js +2 -1
  140. package/dist/features/reviews/components/ReviewDetailPageView.d.ts +4 -0
  141. package/dist/features/reviews/components/ReviewDetailPageView.js +20 -0
  142. package/dist/features/reviews/components/ReviewDetailShell.d.ts +7 -0
  143. package/dist/features/reviews/components/ReviewDetailShell.js +80 -0
  144. package/dist/features/reviews/components/ReviewFilters.d.ts +3 -3
  145. package/dist/features/reviews/components/ReviewFilters.js +5 -4
  146. package/dist/features/reviews/components/ReviewsIndexListing.d.ts +4 -3
  147. package/dist/features/reviews/components/ReviewsIndexListing.js +35 -51
  148. package/dist/features/reviews/components/ReviewsIndexPageView.d.ts +6 -1
  149. package/dist/features/reviews/components/ReviewsIndexPageView.js +49 -3
  150. package/dist/features/reviews/components/ReviewsList.js +9 -1
  151. package/dist/features/reviews/components/index.d.ts +1 -0
  152. package/dist/features/reviews/hooks/useReviews.js +15 -1
  153. package/dist/features/reviews/types/index.d.ts +6 -1
  154. package/dist/features/seller/components/SellerSidebar.d.ts +8 -4
  155. package/dist/features/seller/components/SellerSidebar.js +6 -4
  156. package/dist/features/seller/components/index.d.ts +30 -0
  157. package/dist/features/seller/components/index.js +17 -0
  158. package/dist/features/seller/hooks/useSellerStore.d.ts +2 -0
  159. package/dist/features/seller/hooks/useSellerStore.js +2 -0
  160. package/dist/features/seller/permission-map.d.ts +4 -2
  161. package/dist/features/seller/permission-map.js +16 -14
  162. package/dist/features/seller/schemas/index.d.ts +2 -2
  163. package/dist/features/stores/api/[storeSlug]/reviews/route.d.ts +1 -1
  164. package/dist/features/stores/api/[storeSlug]/reviews/route.js +24 -19
  165. package/dist/features/stores/components/InteractiveStoreCard.d.ts +0 -5
  166. package/dist/features/stores/components/InteractiveStoreCard.js +9 -9
  167. package/dist/features/stores/components/StoreAuctionsListing.js +27 -9
  168. package/dist/features/stores/components/StoreDetailLayoutView.js +2 -0
  169. package/dist/features/stores/components/StoreFilters.d.ts +5 -0
  170. package/dist/features/stores/components/StoreFilters.js +20 -0
  171. package/dist/features/stores/components/StoreHeader.js +2 -2
  172. package/dist/features/stores/components/StorePreOrdersListing.d.ts +5 -0
  173. package/dist/features/stores/components/StorePreOrdersListing.js +40 -0
  174. package/dist/features/stores/components/StorePreOrdersPageView.d.ts +4 -0
  175. package/dist/features/stores/components/StorePreOrdersPageView.js +21 -0
  176. package/dist/features/stores/components/StoreProductsListing.js +21 -11
  177. package/dist/features/stores/components/StoreReviewsListing.js +2 -7
  178. package/dist/features/stores/components/StoresIndexListing.js +42 -8
  179. package/dist/features/stores/components/StoresIndexPageView.d.ts +6 -1
  180. package/dist/features/stores/components/StoresIndexPageView.js +9 -2
  181. package/dist/features/stores/components/index.d.ts +3 -0
  182. package/dist/features/stores/components/index.js +1 -0
  183. package/dist/features/stores/hooks/useStores.d.ts +7 -1
  184. package/dist/features/stores/hooks/useStores.js +16 -3
  185. package/dist/features/stores/schemas/index.d.ts +2 -2
  186. package/dist/features/stores/types/index.d.ts +3 -0
  187. package/dist/features/wishlist/hooks/useGuestWishlist.d.ts +20 -0
  188. package/dist/features/wishlist/hooks/useGuestWishlist.js +49 -0
  189. package/dist/features/wishlist/hooks/useWishlistCount.d.ts +7 -0
  190. package/dist/features/wishlist/hooks/useWishlistCount.js +31 -0
  191. package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +56 -0
  192. package/dist/features/wishlist/hooks/useWishlistWithGuest.js +57 -0
  193. package/dist/features/wishlist/index.d.ts +3 -0
  194. package/dist/features/wishlist/index.js +3 -0
  195. package/dist/features/wishlist/utils/guest-wishlist.d.ts +22 -0
  196. package/dist/features/wishlist/utils/guest-wishlist.js +70 -0
  197. package/dist/index.d.ts +50 -1
  198. package/dist/index.js +63 -1
  199. package/dist/next/routing/route-map.d.ts +70 -36
  200. package/dist/next/routing/route-map.js +30 -22
  201. package/dist/seed/addresses-seed-data.js +62 -261
  202. package/dist/seed/beyblade-seed-data.d.ts +7 -0
  203. package/dist/seed/beyblade-seed-data.js +947 -0
  204. package/dist/seed/bids-seed-data.d.ts +10 -2
  205. package/dist/seed/bids-seed-data.js +220 -1071
  206. package/dist/seed/blog-posts-seed-data.d.ts +2 -2
  207. package/dist/seed/blog-posts-seed-data.js +455 -117
  208. package/dist/seed/cart-seed-data.d.ts +9 -9
  209. package/dist/seed/cart-seed-data.js +73 -74
  210. package/dist/seed/coupons-seed-data.d.ts +3 -4
  211. package/dist/seed/coupons-seed-data.js +3 -509
  212. package/dist/seed/events-seed-data.d.ts +2 -2
  213. package/dist/seed/events-seed-data.js +315 -476
  214. package/dist/seed/faq-seed-data.d.ts +18 -41
  215. package/dist/seed/faq-seed-data.js +1059 -1172
  216. package/dist/seed/hot-wheels-seed-data.d.ts +7 -0
  217. package/dist/seed/hot-wheels-seed-data.js +1365 -0
  218. package/dist/seed/index.d.ts +6 -1
  219. package/dist/seed/index.js +6 -1
  220. package/dist/seed/pokemon-carousel-slides-seed-data.d.ts +4 -2
  221. package/dist/seed/pokemon-carousel-slides-seed-data.js +152 -268
  222. package/dist/seed/pokemon-categories-seed-data.d.ts +18 -21
  223. package/dist/seed/pokemon-categories-seed-data.js +424 -1004
  224. package/dist/seed/pokemon-coupons-seed-data.d.ts +6 -0
  225. package/dist/seed/pokemon-coupons-seed-data.js +465 -0
  226. package/dist/seed/pokemon-homepage-sections-seed-data.d.ts +3 -2
  227. package/dist/seed/pokemon-homepage-sections-seed-data.js +67 -289
  228. package/dist/seed/pokemon-products-seed-data.js +662 -0
  229. package/dist/seed/pokemon-seed-bundle.d.ts +32 -11
  230. package/dist/seed/pokemon-seed-bundle.js +41 -11
  231. package/dist/seed/pokemon-stores-seed-data.d.ts +2 -3
  232. package/dist/seed/pokemon-stores-seed-data.js +56 -31
  233. package/dist/seed/pokemon-users-seed-data.d.ts +2 -2
  234. package/dist/seed/pokemon-users-seed-data.js +245 -261
  235. package/dist/seed/reviews-seed-data.d.ts +17 -2
  236. package/dist/seed/reviews-seed-data.js +519 -483
  237. package/dist/seed/site-settings-seed-data.js +14 -14
  238. package/dist/seed/store-addresses-seed-data.js +68 -50
  239. package/dist/seed/transformers-seed-data.d.ts +7 -0
  240. package/dist/seed/transformers-seed-data.js +510 -0
  241. package/dist/seed/wishlists-seed-data.d.ts +5 -1
  242. package/dist/seed/wishlists-seed-data.js +82 -4
  243. package/dist/server.d.ts +1 -0
  244. package/dist/server.js +2 -0
  245. package/dist/tokens/index.d.ts +6 -0
  246. package/dist/tokens/index.js +2 -0
  247. package/dist/ui/components/BaseListingCard.js +24 -26
  248. package/dist/ui/components/BaseListingCard.style.css +5 -5
  249. package/dist/ui/components/HorizontalScroller.d.ts +1 -1
  250. package/dist/ui/components/HorizontalScroller.js +19 -5
  251. package/dist/ui/components/SideDrawer.style.css +3 -11
  252. package/dist/ui/rich-text/RichText.js +19 -1
  253. package/package.json +1 -1
@@ -7,9 +7,21 @@ import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } fr
7
7
  import { AdminListingScaffold } from "./AdminListingScaffold";
8
8
  export function AdminCategoriesView({ children, ...props }) {
9
9
  const hasChildren = React.Children.count(children) > 0;
10
+ const [q, setQ] = React.useState("");
11
+ const [activeFilter, setActiveFilter] = React.useState("");
12
+ const [featuredFilter, setFeaturedFilter] = React.useState("");
13
+ const filterParts = [];
14
+ if (activeFilter && activeFilter !== "All") {
15
+ filterParts.push(activeFilter === "Active" ? "isActive==true" : "isActive==false");
16
+ }
17
+ if (featuredFilter && featuredFilter !== "All")
18
+ filterParts.push("isFeatured==true");
19
+ const filters = filterParts.join(",") || undefined;
10
20
  const { rows, total, isLoading, errorMessage } = useAdminListingData({
11
- queryKey: ["admin", "categories", "listing"],
21
+ queryKey: ["admin", "categories", "listing", q, filters ?? ""],
12
22
  endpoint: `${CATEGORY_ENDPOINTS.LIST}?flat=true`,
23
+ filters,
24
+ q,
13
25
  mapRows: (response) => {
14
26
  const sourceItems = Array.isArray(response.data)
15
27
  ? response.data
@@ -37,5 +49,18 @@ export function AdminCategoriesView({ children, ...props }) {
37
49
  if (hasChildren) {
38
50
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
39
51
  }
40
- return (_jsx(AdminListingScaffold, { portal: "admin", ...props, title: "Category Management", subtitle: "Organize taxonomy hierarchy, slug readiness, and active visibility from the shared listing scaffold.", actionLabel: "New category", searchPlaceholder: "Search categories, slugs, or parent category", rows: rows, isLoading: isLoading, errorMessage: errorMessage, emptyLabel: "No categories found", resultSummary: `Showing ${rows.length} of ${total} categories` }));
52
+ return (_jsx(AdminListingScaffold, { portal: "admin", ...props, title: "Category Management", subtitle: "Organize taxonomy hierarchy, slug readiness, and active visibility from the shared listing scaffold.", actionLabel: "New category", searchPlaceholder: "Search categories, slugs, or parent category", onSearch: setQ, searchValue: q, rows: rows, isLoading: isLoading, errorMessage: errorMessage, emptyLabel: "No categories found", resultSummary: `Showing ${rows.length} of ${total} categories`, filterGroups: [
53
+ {
54
+ title: "Active",
55
+ options: ["All", "Active", "Inactive"],
56
+ active: activeFilter || "All",
57
+ onSelect: (opt) => setActiveFilter(opt === "All" ? "" : opt),
58
+ },
59
+ {
60
+ title: "Featured",
61
+ options: ["All", "Featured Only"],
62
+ active: featuredFilter || "All",
63
+ onSelect: (opt) => setFeaturedFilter(opt === "All" ? "" : opt),
64
+ },
65
+ ] }));
41
66
  }
@@ -0,0 +1,4 @@
1
+ import type { ListingViewShellProps } from "../../../ui";
2
+ export interface AdminContactViewProps extends ListingViewShellProps {
3
+ }
4
+ export declare function AdminContactView({ children, ...props }: AdminContactViewProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,50 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import React from "react";
4
+ import { ListingViewShell } from "../../../ui";
5
+ import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
6
+ import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
7
+ import { AdminListingScaffold } from "./AdminListingScaffold";
8
+ export function AdminContactView({ children, ...props }) {
9
+ const hasChildren = React.Children.count(children) > 0;
10
+ const [q, setQ] = React.useState("");
11
+ const [statusFilter, setStatusFilter] = React.useState("");
12
+ const filterParts = [];
13
+ if (statusFilter && statusFilter !== "All")
14
+ filterParts.push(`status==${statusFilter}`);
15
+ const filters = filterParts.join(",") || undefined;
16
+ const { rows, total, isLoading, errorMessage } = useAdminListingData({
17
+ queryKey: ["admin", "contact", "listing", q, filters ?? ""],
18
+ endpoint: ADMIN_ENDPOINTS.CONTACT_SUBMISSIONS,
19
+ filters,
20
+ q,
21
+ mapRows: (response) => toRecordArray(response.submissions).map((item, index) => ({
22
+ id: toStringValue(item.id, `msg-${index}`),
23
+ primary: toStringValue(item.subject, "No subject"),
24
+ secondary: [
25
+ toStringValue(item.name, "Unknown"),
26
+ toStringValue(item.email, ""),
27
+ ].filter(Boolean).join(" · ") || "—",
28
+ status: toStringValue(item.status, "new"),
29
+ updatedAt: toRelativeDate(item.createdAt),
30
+ })),
31
+ getTotal: (response, mappedRows) => {
32
+ if (typeof response.meta?.filteredTotal === "number")
33
+ return response.meta.filteredTotal;
34
+ if (typeof response.meta?.total === "number")
35
+ return response.meta.total;
36
+ return mappedRows.length;
37
+ },
38
+ });
39
+ if (hasChildren) {
40
+ return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
41
+ }
42
+ return (_jsx(AdminListingScaffold, { portal: "admin", ...props, title: "Contact Submissions", subtitle: "Review inbound contact form messages from users and visitors. Filter by status and search by subject or sender.", searchPlaceholder: "Search by subject, name, or email", onSearch: setQ, searchValue: q, rows: rows, isLoading: isLoading, errorMessage: errorMessage, emptyLabel: "No contact submissions found", resultSummary: `Showing ${rows.length} of ${total} submissions`, filterGroups: [
43
+ {
44
+ title: "Status",
45
+ options: ["All", "new", "read", "resolved"],
46
+ active: statusFilter || "All",
47
+ onSelect: (opt) => setStatusFilter(opt === "All" ? "" : opt),
48
+ },
49
+ ] }));
50
+ }
@@ -7,9 +7,19 @@ import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } fr
7
7
  import { AdminListingScaffold } from "./AdminListingScaffold";
8
8
  export function AdminFaqsView({ children, ...props }) {
9
9
  const hasChildren = React.Children.count(children) > 0;
10
+ const [q, setQ] = React.useState("");
11
+ const [activeFilter, setActiveFilter] = React.useState("");
12
+ const filterParts = [];
13
+ if (activeFilter === "Active")
14
+ filterParts.push("isActive==true");
15
+ else if (activeFilter === "Inactive")
16
+ filterParts.push("isActive==false");
17
+ const filters = filterParts.join(",") || undefined;
10
18
  const { rows, total, isLoading, errorMessage } = useAdminListingData({
11
- queryKey: ["admin", "faqs", "listing"],
19
+ queryKey: ["admin", "faqs", "listing", q, filters ?? ""],
12
20
  endpoint: ADMIN_ENDPOINTS.FAQS,
21
+ filters,
22
+ q,
13
23
  mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
14
24
  id: toStringValue(item.id, `faq-${index}`),
15
25
  primary: toStringValue(item.question, "Untitled question"),
@@ -22,5 +32,12 @@ export function AdminFaqsView({ children, ...props }) {
22
32
  if (hasChildren) {
23
33
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
24
34
  }
25
- return (_jsx(AdminListingScaffold, { portal: "admin", ...props, title: "FAQ Library", subtitle: "Keep support answers organized by category, visibility, and homepage priority.", actionLabel: "New FAQ", searchPlaceholder: "Search questions, categories, or tokens", rows: rows, isLoading: isLoading, errorMessage: errorMessage, emptyLabel: "No FAQs found", resultSummary: `Showing ${rows.length} of ${total} FAQs` }));
35
+ return (_jsx(AdminListingScaffold, { portal: "admin", ...props, title: "FAQ Library", subtitle: "Keep support answers organized by category, visibility, and homepage priority.", actionLabel: "New FAQ", searchPlaceholder: "Search questions, categories, or tokens", onSearch: setQ, searchValue: q, rows: rows, isLoading: isLoading, errorMessage: errorMessage, emptyLabel: "No FAQs found", resultSummary: `Showing ${rows.length} of ${total} FAQs`, filterGroups: [
36
+ {
37
+ title: "Status",
38
+ options: ["All", "Active", "Inactive"],
39
+ active: activeFilter || "All",
40
+ onSelect: (opt) => setActiveFilter(opt === "All" ? "" : opt),
41
+ },
42
+ ] }));
26
43
  }
@@ -1,4 +1,5 @@
1
1
  import { type ListingViewShellProps } from "../../../ui";
2
+ import type { AdminTableColumn } from "../types";
2
3
  export interface AdminListingScaffoldRow {
3
4
  id: string;
4
5
  primary: string;
@@ -9,7 +10,10 @@ export interface AdminListingScaffoldRow {
9
10
  interface AdminListingScaffoldProps extends ListingViewShellProps {
10
11
  title: string;
11
12
  subtitle: string;
12
- actionLabel: string;
13
+ actionLabel?: string;
14
+ actionHref?: string;
15
+ columns?: AdminTableColumn<AdminListingScaffoldRow>[];
16
+ getRowHref?: (row: AdminListingScaffoldRow) => string;
13
17
  searchPlaceholder: string;
14
18
  rows: AdminListingScaffoldRow[];
15
19
  isLoading?: boolean;
@@ -18,9 +22,14 @@ interface AdminListingScaffoldProps extends ListingViewShellProps {
18
22
  filterGroups?: ReadonlyArray<{
19
23
  title: string;
20
24
  options: ReadonlyArray<string>;
25
+ active?: string;
26
+ onSelect?: (option: string) => void;
21
27
  }>;
22
28
  activeFilters?: string[];
23
29
  resultSummary?: string;
30
+ onSearch?: (query: string) => void;
31
+ searchValue?: string;
32
+ onClearFilters?: () => void;
24
33
  }
25
- export declare function AdminListingScaffold({ title, subtitle, actionLabel, searchPlaceholder, rows, isLoading, errorMessage, emptyLabel, filterGroups, activeFilters, resultSummary, children, headerSlot, filterContent, activeFiltersSlot, resultCountSlot, searchSlot, sortSlot, actionsSlot, ...props }: AdminListingScaffoldProps): import("react/jsx-runtime").JSX.Element;
34
+ export declare function AdminListingScaffold({ title, subtitle, actionLabel, actionHref, columns, getRowHref, searchPlaceholder, rows, isLoading, errorMessage, emptyLabel, filterGroups, activeFilters, resultSummary, onSearch, searchValue, onClearFilters, children, headerSlot, filterContent, activeFiltersSlot, resultCountSlot, searchSlot, sortSlot, actionsSlot, ...props }: AdminListingScaffoldProps): import("react/jsx-runtime").JSX.Element;
26
35
  export {};
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import React from "react";
3
4
  import { Button, Div, Input, ListingViewShell, Span, Text, } from "../../../ui";
@@ -38,7 +39,7 @@ function buildColumns() {
38
39
  ];
39
40
  }
40
41
  function renderFilterContent(groups = DEFAULT_FILTER_GROUPS) {
41
- return (_jsx(Div, { className: "space-y-5", children: groups.map((group) => (_jsxs(Div, { className: "space-y-2", children: [_jsx(Text, { className: "text-xs font-semibold uppercase tracking-[0.18em] text-zinc-500 dark:text-zinc-400", children: group.title }), _jsx(Div, { className: "flex flex-wrap gap-2", children: group.options.map((option, index) => (_jsx(Button, { type: "button", variant: "outline", size: "sm", className: "rounded-full", children: option }, `${group.title}-${option}`))) })] }, group.title))) }));
42
+ return (_jsx(Div, { className: "space-y-5", children: groups.map((group) => (_jsxs(Div, { className: "space-y-2", children: [_jsx(Text, { className: "text-xs font-semibold uppercase tracking-[0.18em] text-zinc-500 dark:text-zinc-400", children: group.title }), _jsx(Div, { className: "flex flex-wrap gap-2", children: group.options.map((option) => (_jsx(Button, { type: "button", variant: group.active === option ? "primary" : "outline", size: "sm", className: "rounded-full", onClick: () => group.onSelect?.(option), children: option }, `${group.title}-${option}`))) })] }, group.title))) }));
42
43
  }
43
44
  function renderActiveFilters(activeFilters) {
44
45
  if (!activeFilters || activeFilters.length === 0) {
@@ -46,12 +47,22 @@ function renderActiveFilters(activeFilters) {
46
47
  }
47
48
  return (_jsx(Div, { className: "mb-4 flex flex-wrap gap-2", children: activeFilters.map((filter) => (_jsx(Span, { className: "inline-flex rounded-full border border-zinc-200 bg-white px-3 py-1 text-xs font-medium text-zinc-600 dark:border-slate-700 dark:bg-slate-900 dark:text-zinc-300", children: filter }, filter))) }));
48
49
  }
49
- export function AdminListingScaffold({ title, subtitle, actionLabel, searchPlaceholder, rows, isLoading, errorMessage, emptyLabel, filterGroups, activeFilters, resultSummary, children, headerSlot, filterContent, activeFiltersSlot, resultCountSlot, searchSlot, sortSlot, actionsSlot, ...props }) {
50
+ export function AdminListingScaffold({ title, subtitle, actionLabel, actionHref, columns, getRowHref, searchPlaceholder, rows, isLoading, errorMessage, emptyLabel, filterGroups, activeFilters, resultSummary, onSearch, searchValue, onClearFilters, children, headerSlot, filterContent, activeFiltersSlot, resultCountSlot, searchSlot, sortSlot, actionsSlot, ...props }) {
51
+ const [localSearch, setLocalSearch] = React.useState(searchValue ?? "");
50
52
  const hasChildren = React.Children.count(children) > 0;
51
53
  return (_jsx(ListingViewShell, { ...props, portal: "admin", headerSlot: headerSlot ?? (_jsx(AdminPageHeader, { title: title, subtitle: subtitle, actionLabel: actionLabel, onAction: () => undefined, themeConfig: {
52
54
  gradient: "rounded-3xl border border-zinc-200 bg-gradient-to-r from-white to-zinc-50 dark:border-slate-800 dark:from-slate-950 dark:to-slate-900",
53
55
  titleClass: "text-3xl font-bold text-zinc-950 dark:text-zinc-50",
54
56
  subtitleClass: "text-sm text-zinc-600 dark:text-zinc-300",
55
57
  spacingClass: "space-y-1.5",
56
- } })), filterContent: filterContent ?? renderFilterContent(filterGroups), activeFiltersSlot: activeFiltersSlot ?? renderActiveFilters(activeFilters), resultCountSlot: resultCountSlot ?? (_jsx(Text, { className: "text-sm text-zinc-600 dark:text-zinc-300", children: resultSummary ?? `Showing ${rows.length} items` })), searchSlot: searchSlot ?? (_jsx(Input, { readOnly: true, value: "", placeholder: searchPlaceholder, "aria-label": searchPlaceholder, className: "min-w-[240px]" })), sortSlot: sortSlot ?? (_jsx(Button, { type: "button", variant: "outline", size: "sm", children: "Newest first" })), actionsSlot: actionsSlot ?? (_jsx(Button, { type: "button", variant: "primary", size: "sm", children: actionLabel })), filterActiveCount: activeFilters?.length ?? 0, filterPendingCount: 0, errorSlot: errorMessage ? (_jsx(Div, { className: "rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })) : undefined, children: hasChildren ? (children) : (_jsx(DataTable, { columns: buildColumns(), rows: rows, isLoading: isLoading, emptyLabel: emptyLabel })) }));
58
+ } })), filterContent: filterContent ?? renderFilterContent(filterGroups), activeFiltersSlot: activeFiltersSlot ?? renderActiveFilters(activeFilters), resultCountSlot: resultCountSlot ?? (_jsx(Text, { className: "text-sm text-zinc-600 dark:text-zinc-300", children: resultSummary ?? `Showing ${rows.length} items` })), searchSlot: searchSlot ?? (_jsx(Input, { value: localSearch, placeholder: searchPlaceholder, "aria-label": searchPlaceholder, className: "min-w-[240px]", onChange: (e) => {
59
+ setLocalSearch(e.target.value);
60
+ }, onKeyDown: (e) => {
61
+ if (e.key === "Enter") {
62
+ onSearch?.(localSearch);
63
+ }
64
+ } })), sortSlot: sortSlot ?? (_jsx(Button, { type: "button", variant: "outline", size: "sm", children: "Newest first" })), actionsSlot: actionsSlot ??
65
+ (actionLabel ? (actionHref ? (_jsx(Button, { type: "button", variant: "primary", size: "sm", onClick: () => {
66
+ window.location.href = actionHref;
67
+ }, children: actionLabel })) : (_jsx(Button, { type: "button", variant: "primary", size: "sm", children: actionLabel }))) : undefined), filterActiveCount: activeFilters?.length ?? 0, filterPendingCount: 0, errorSlot: errorMessage ? (_jsx(Div, { className: "rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })) : undefined, children: hasChildren ? (children) : (_jsx(DataTable, { columns: columns ?? buildColumns(), rows: rows, isLoading: isLoading, emptyLabel: emptyLabel, getRowHref: getRowHref })) }));
57
68
  }
@@ -0,0 +1,4 @@
1
+ import type { ListingViewShellProps } from "../../../ui";
2
+ export interface AdminNewsletterViewProps extends ListingViewShellProps {
3
+ }
4
+ export declare function AdminNewsletterView({ children, ...props }: AdminNewsletterViewProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,50 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import React from "react";
4
+ import { ListingViewShell } from "../../../ui";
5
+ import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
6
+ import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
7
+ import { AdminListingScaffold } from "./AdminListingScaffold";
8
+ export function AdminNewsletterView({ children, ...props }) {
9
+ const hasChildren = React.Children.count(children) > 0;
10
+ const [q, setQ] = React.useState("");
11
+ const [statusFilter, setStatusFilter] = React.useState("");
12
+ const filterParts = [];
13
+ if (statusFilter && statusFilter !== "All")
14
+ filterParts.push(`status==${statusFilter}`);
15
+ const filters = filterParts.join(",") || undefined;
16
+ const { rows, total, isLoading, errorMessage } = useAdminListingData({
17
+ queryKey: ["admin", "newsletter", "listing", q, filters ?? ""],
18
+ endpoint: ADMIN_ENDPOINTS.NEWSLETTER,
19
+ filters,
20
+ q,
21
+ mapRows: (response) => toRecordArray(response.subscribers).map((item, index) => ({
22
+ id: toStringValue(item.id, `sub-${index}`),
23
+ primary: toStringValue(item.email, "Unknown email"),
24
+ secondary: [
25
+ toStringValue(item.source, ""),
26
+ item.ipAddress ? "IP logged" : "",
27
+ ].filter(Boolean).join(" · ") || "—",
28
+ status: toStringValue(item.status, "unknown"),
29
+ updatedAt: toRelativeDate(item.subscribedAt ?? item.createdAt),
30
+ })),
31
+ getTotal: (response, mappedRows) => {
32
+ if (typeof response.meta?.filteredTotal === "number")
33
+ return response.meta.filteredTotal;
34
+ if (typeof response.meta?.total === "number")
35
+ return response.meta.total;
36
+ return mappedRows.length;
37
+ },
38
+ });
39
+ if (hasChildren) {
40
+ return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
41
+ }
42
+ return (_jsx(AdminListingScaffold, { portal: "admin", ...props, title: "Newsletter Subscribers", subtitle: "View and manage email subscribers. Filter by status, search by email, and track subscription sources.", searchPlaceholder: "Search by email or source", onSearch: setQ, searchValue: q, rows: rows, isLoading: isLoading, errorMessage: errorMessage, emptyLabel: "No subscribers found", resultSummary: `Showing ${rows.length} of ${total} subscribers`, filterGroups: [
43
+ {
44
+ title: "Status",
45
+ options: ["All", "active", "unsubscribed"],
46
+ active: statusFilter || "All",
47
+ onSelect: (opt) => setStatusFilter(opt === "All" ? "" : opt),
48
+ },
49
+ ] }));
50
+ }
@@ -7,9 +7,24 @@ import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } fr
7
7
  import { AdminListingScaffold } from "./AdminListingScaffold";
8
8
  export function AdminProductsView({ children, ...props }) {
9
9
  const hasChildren = React.Children.count(children) > 0;
10
+ const [q, setQ] = React.useState("");
11
+ const [statusFilter, setStatusFilter] = React.useState("");
12
+ const [typeFilter, setTypeFilter] = React.useState("");
13
+ const filterParts = [];
14
+ if (statusFilter && statusFilter !== "All")
15
+ filterParts.push(`status==${statusFilter}`);
16
+ if (typeFilter && typeFilter !== "All") {
17
+ if (typeFilter === "Auctions")
18
+ filterParts.push("isAuction==true");
19
+ else if (typeFilter === "Pre-orders")
20
+ filterParts.push("isPreOrder==true");
21
+ }
22
+ const filters = filterParts.join(",") || undefined;
10
23
  const { rows, total, isLoading, errorMessage } = useAdminListingData({
11
- queryKey: ["admin", "products", "listing"],
24
+ queryKey: ["admin", "products", "listing", q, filters ?? ""],
12
25
  endpoint: ADMIN_ENDPOINTS.PRODUCTS,
26
+ filters,
27
+ q,
13
28
  mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
14
29
  id: toStringValue(item.id, `product-${index}`),
15
30
  primary: toStringValue(item.title ?? item.name, "Untitled product"),
@@ -25,5 +40,18 @@ export function AdminProductsView({ children, ...props }) {
25
40
  if (hasChildren) {
26
41
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
27
42
  }
28
- return (_jsx(AdminListingScaffold, { portal: "admin", ...props, title: "Product Management", subtitle: "Review catalogue health, publishing state, and merchandising issues from one queue.", actionLabel: "New product", searchPlaceholder: "Search products, SKUs, or seller names", rows: rows, isLoading: isLoading, errorMessage: errorMessage, emptyLabel: "No products found", resultSummary: `Showing ${rows.length} of ${total} products` }));
43
+ return (_jsx(AdminListingScaffold, { portal: "admin", ...props, title: "Product Management", subtitle: "Review catalogue health, publishing state, and merchandising issues from one queue.", actionLabel: "New product", searchPlaceholder: "Search products, SKUs, or seller names", onSearch: setQ, searchValue: q, rows: rows, isLoading: isLoading, errorMessage: errorMessage, emptyLabel: "No products found", resultSummary: `Showing ${rows.length} of ${total} products`, filterGroups: [
44
+ {
45
+ title: "Status",
46
+ options: ["All", "published", "draft", "archived"],
47
+ active: statusFilter || "All",
48
+ onSelect: (opt) => setStatusFilter(opt === "All" ? "" : opt),
49
+ },
50
+ {
51
+ title: "Type",
52
+ options: ["All", "Products", "Auctions", "Pre-orders"],
53
+ active: typeFilter || "All",
54
+ onSelect: (opt) => setTypeFilter(opt === "All" ? "" : opt),
55
+ },
56
+ ] }));
29
57
  }
@@ -8,9 +8,20 @@ import { AdminListingScaffold } from "./AdminListingScaffold";
8
8
  export function AdminReviewsView({ renderDetailView, children, ...props }) {
9
9
  const hasChildren = React.Children.count(children) > 0;
10
10
  const hasDetailView = Boolean(renderDetailView);
11
+ const [q, setQ] = React.useState("");
12
+ const [statusFilter, setStatusFilter] = React.useState("");
13
+ const [ratingFilter, setRatingFilter] = React.useState("");
14
+ const filterParts = [];
15
+ if (statusFilter && statusFilter !== "All")
16
+ filterParts.push(`status==${statusFilter}`);
17
+ if (ratingFilter && ratingFilter !== "All")
18
+ filterParts.push(`rating==${ratingFilter}`);
19
+ const filters = filterParts.join(",") || undefined;
11
20
  const { rows, total, isLoading, errorMessage } = useAdminListingData({
12
- queryKey: ["admin", "reviews", "listing"],
21
+ queryKey: ["admin", "reviews", "listing", q, filters ?? ""],
13
22
  endpoint: ADMIN_ENDPOINTS.REVIEWS,
23
+ filters,
24
+ q,
14
25
  mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
15
26
  id: toStringValue(item.id, `review-${index}`),
16
27
  primary: `${toStringValue(item.rating, "-")} star · ${toStringValue(item.productTitle ?? item.productName, "Unknown product")}`,
@@ -23,5 +34,18 @@ export function AdminReviewsView({ renderDetailView, children, ...props }) {
23
34
  if (hasChildren || hasDetailView) {
24
35
  return _jsx(ListingViewShell, { portal: "admin", ...props, detailView: renderDetailView?.(), children: children });
25
36
  }
26
- return (_jsx(AdminListingScaffold, { portal: "admin", ...props, title: "Review Moderation", subtitle: "Moderate customer feedback, seller responses, and featured review placement from one queue.", actionLabel: "Review policies", searchPlaceholder: "Search reviews, products, or seller names", rows: rows, isLoading: isLoading, errorMessage: errorMessage, emptyLabel: "No reviews found", resultSummary: `Showing ${rows.length} of ${total} reviews` }));
37
+ return (_jsx(AdminListingScaffold, { portal: "admin", ...props, title: "Review Moderation", subtitle: "Moderate customer feedback, seller responses, and featured review placement from one queue.", actionLabel: "Review policies", searchPlaceholder: "Search reviews, products, or seller names", onSearch: setQ, searchValue: q, rows: rows, isLoading: isLoading, errorMessage: errorMessage, emptyLabel: "No reviews found", resultSummary: `Showing ${rows.length} of ${total} reviews`, filterGroups: [
38
+ {
39
+ title: "Status",
40
+ options: ["All", "approved", "pending", "rejected"],
41
+ active: statusFilter || "All",
42
+ onSelect: (opt) => setStatusFilter(opt === "All" ? "" : opt),
43
+ },
44
+ {
45
+ title: "Rating",
46
+ options: ["All", "5", "4", "3", "2", "1"],
47
+ active: ratingFilter || "All",
48
+ onSelect: (opt) => setRatingFilter(opt === "All" ? "" : opt),
49
+ },
50
+ ] }));
27
51
  }
@@ -7,9 +7,17 @@ import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } fr
7
7
  import { AdminListingScaffold } from "./AdminListingScaffold";
8
8
  export function AdminStoresView({ children, ...props }) {
9
9
  const hasChildren = React.Children.count(children) > 0;
10
+ const [q, setQ] = React.useState("");
11
+ const [statusFilter, setStatusFilter] = React.useState("");
12
+ const filterParts = [];
13
+ if (statusFilter && statusFilter !== "All")
14
+ filterParts.push(`status==${statusFilter}`);
15
+ const filters = filterParts.join(",") || undefined;
10
16
  const { rows, total, isLoading, errorMessage } = useAdminListingData({
11
- queryKey: ["admin", "stores", "listing"],
17
+ queryKey: ["admin", "stores", "listing", q, filters ?? ""],
12
18
  endpoint: ADMIN_ENDPOINTS.STORES,
19
+ filters,
20
+ q,
13
21
  mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
14
22
  id: toStringValue(item.id, `store-${index}`),
15
23
  primary: toStringValue(item.storeName, "Unnamed store"),
@@ -25,5 +33,12 @@ export function AdminStoresView({ children, ...props }) {
25
33
  if (hasChildren) {
26
34
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
27
35
  }
28
- return (_jsx(AdminListingScaffold, { portal: "admin", ...props, title: "Store Directory", subtitle: "Review seller storefront health, verification, and merchandising quality from the shared listing shell.", actionLabel: "Approve store", searchPlaceholder: "Search stores, slugs, or owner names", rows: rows, isLoading: isLoading, errorMessage: errorMessage, emptyLabel: "No stores found", resultSummary: `Showing ${rows.length} of ${total} stores` }));
36
+ return (_jsx(AdminListingScaffold, { portal: "admin", ...props, title: "Store Directory", subtitle: "Review seller storefront health, verification, and merchandising quality from the shared listing shell.", actionLabel: "Approve store", searchPlaceholder: "Search stores, slugs, or owner names", onSearch: setQ, searchValue: q, rows: rows, isLoading: isLoading, errorMessage: errorMessage, emptyLabel: "No stores found", resultSummary: `Showing ${rows.length} of ${total} stores`, filterGroups: [
37
+ {
38
+ title: "Status",
39
+ options: ["All", "active", "pending", "suspended", "rejected"],
40
+ active: statusFilter || "All",
41
+ onSelect: (opt) => setStatusFilter(opt === "All" ? "" : opt),
42
+ },
43
+ ] }));
29
44
  }
@@ -7,9 +7,21 @@ import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } fr
7
7
  import { AdminListingScaffold } from "./AdminListingScaffold";
8
8
  export function AdminUsersView({ children, ...props }) {
9
9
  const hasChildren = React.Children.count(children) > 0;
10
+ const [q, setQ] = React.useState("");
11
+ const [statusFilter, setStatusFilter] = React.useState("");
12
+ const [roleFilter, setRoleFilter] = React.useState("");
13
+ const filterParts = [];
14
+ if (statusFilter && statusFilter !== "All") {
15
+ filterParts.push(statusFilter === "Active" ? "disabled==false" : "disabled==true");
16
+ }
17
+ if (roleFilter && roleFilter !== "All")
18
+ filterParts.push(`role==${roleFilter}`);
19
+ const filters = filterParts.join(",") || undefined;
10
20
  const { rows, total, isLoading, errorMessage } = useAdminListingData({
11
- queryKey: ["admin", "users", "listing"],
21
+ queryKey: ["admin", "users", "listing", q, filters ?? ""],
12
22
  endpoint: ADMIN_ENDPOINTS.USERS,
23
+ filters,
24
+ q,
13
25
  mapRows: (response) => toRecordArray(response.users).map((item, index) => ({
14
26
  id: toStringValue(item.id ?? item.uid, `user-${index}`),
15
27
  primary: toStringValue(item.displayName, "Unnamed user"),
@@ -37,5 +49,18 @@ export function AdminUsersView({ children, ...props }) {
37
49
  if (hasChildren) {
38
50
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
39
51
  }
40
- return (_jsx(AdminListingScaffold, { portal: "admin", ...props, title: "User Management", subtitle: "Audit onboarding, verification, and role changes without leaving the admin listing shell.", actionLabel: "Invite user", searchPlaceholder: "Search users, email, or seller handles", rows: rows, isLoading: isLoading, errorMessage: errorMessage, emptyLabel: "No users found", resultSummary: `Showing ${rows.length} of ${total} users` }));
52
+ return (_jsx(AdminListingScaffold, { portal: "admin", ...props, title: "User Management", subtitle: "Audit onboarding, verification, and role changes without leaving the admin listing shell.", actionLabel: "Invite user", searchPlaceholder: "Search users, email, or seller handles", onSearch: setQ, searchValue: q, rows: rows, isLoading: isLoading, errorMessage: errorMessage, emptyLabel: "No users found", resultSummary: `Showing ${rows.length} of ${total} users`, filterGroups: [
53
+ {
54
+ title: "Status",
55
+ options: ["All", "Active", "Disabled"],
56
+ active: statusFilter || "All",
57
+ onSelect: (opt) => setStatusFilter(opt === "All" ? "" : opt),
58
+ },
59
+ {
60
+ title: "Role",
61
+ options: ["All", "admin", "seller", "buyer", "moderator"],
62
+ active: roleFilter || "All",
63
+ onSelect: (opt) => setRoleFilter(opt === "All" ? "" : opt),
64
+ },
65
+ ] }));
41
66
  }
@@ -5,6 +5,7 @@ interface DataTableProps<T extends {
5
5
  columns: AdminTableColumn<T>[];
6
6
  rows: T[];
7
7
  isLoading?: boolean;
8
+ getRowHref?: (row: T) => string;
8
9
  sortKey?: string;
9
10
  sortDir?: "asc" | "desc";
10
11
  onSort?: (key: string) => void;
@@ -15,5 +16,5 @@ interface DataTableProps<T extends {
15
16
  }
16
17
  export declare function DataTable<T extends {
17
18
  id: string;
18
- }>({ columns, rows, isLoading, sortKey, sortDir, onSort, totalPages, currentPage, onPageChange, emptyLabel, }: DataTableProps<T>): import("react/jsx-runtime").JSX.Element;
19
+ }>({ columns, rows, isLoading, sortKey, sortDir, onSort, totalPages, currentPage, onPageChange, emptyLabel, getRowHref, }: DataTableProps<T>): import("react/jsx-runtime").JSX.Element;
19
20
  export {};
@@ -1,7 +1,21 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { Button, Div, Span } from "../../../ui";
3
- export function DataTable({ columns, rows, isLoading, sortKey, sortDir, onSort, totalPages = 1, currentPage = 1, onPageChange, emptyLabel = "No records found", }) {
4
- return (_jsxs(Div, { className: "overflow-hidden rounded-xl border border-neutral-200 dark:border-slate-700 bg-white dark:bg-slate-900", children: [_jsx(Div, { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { children: _jsx("tr", { className: "border-b border-neutral-200 dark:border-slate-700 bg-neutral-50 dark:bg-slate-800", children: columns.map((col) => (_jsxs("th", { scope: "col", onClick: col.sortable && onSort ? () => onSort(col.key) : undefined, className: `px-4 py-3 text-left font-semibold text-neutral-900 dark:text-zinc-100 ${col.sortable && onSort ? "cursor-pointer select-none hover:text-primary" : ""} ${col.className ?? ""}`, children: [col.header, col.sortable && sortKey === col.key && (_jsx(Span, { className: "ml-1", children: sortDir === "asc" ? "↑" : "↓" }))] }, col.key))) }) }), _jsx("tbody", { children: isLoading ? (Array.from({ length: 5 }).map((_, i) => (_jsx("tr", { className: "border-b border-neutral-100 dark:border-slate-700", children: columns.map((col) => (_jsx("td", { className: "px-4 py-3", children: _jsx(Div, { className: "h-4 w-full animate-pulse rounded bg-neutral-200 dark:bg-slate-700" }) }, col.key))) }, i)))) : rows.length === 0 ? (_jsx("tr", { children: _jsx("td", { colSpan: columns.length, className: "px-4 py-12 text-center text-neutral-500 dark:text-zinc-400", children: emptyLabel }) })) : (rows.map((row) => (_jsx("tr", { className: "border-b border-neutral-100 dark:border-slate-700 hover:bg-neutral-50 dark:hover:bg-slate-800", children: columns.map((col) => (_jsx("td", { className: `px-4 py-3 text-neutral-700 dark:text-zinc-300 ${col.className ?? ""}`, children: col.render
5
- ? col.render(row)
6
- : String(row[col.key] ?? "") }, col.key))) }, row.id)))) })] }) }), totalPages > 1 && onPageChange && (_jsx(Div, { className: "flex items-center justify-end gap-2 border-t border-neutral-200 dark:border-slate-700 px-4 py-3", children: Array.from({ length: totalPages }, (_, i) => i + 1).map((p) => (_jsx(Button, { onClick: () => onPageChange(p), variant: p === currentPage ? "primary" : "ghost", size: "sm", className: `h-8 w-8 rounded text-xs font-medium transition ${p === currentPage ? "bg-neutral-900 text-white" : "text-neutral-600 dark:text-zinc-300 hover:bg-neutral-100 dark:hover:bg-slate-800"}`, children: p }, p))) }))] }));
4
+ export function DataTable({ columns, rows, isLoading, sortKey, sortDir, onSort, totalPages = 1, currentPage = 1, onPageChange, emptyLabel = "No records found", getRowHref, }) {
5
+ return (_jsxs(Div, { className: "overflow-hidden rounded-xl border border-neutral-200 dark:border-slate-700 bg-white dark:bg-slate-900", children: [_jsx(Div, { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { children: _jsx("tr", { className: "border-b border-neutral-200 dark:border-slate-700 bg-neutral-50 dark:bg-slate-800", children: columns.map((col) => (_jsxs("th", { scope: "col", onClick: col.sortable && onSort ? () => onSort(col.key) : undefined, className: `px-4 py-3 text-left font-semibold text-neutral-900 dark:text-zinc-100 ${col.sortable && onSort ? "cursor-pointer select-none hover:text-primary" : ""} ${col.className ?? ""}`, children: [col.header, col.sortable && sortKey === col.key && (_jsx(Span, { className: "ml-1", children: sortDir === "asc" ? "↑" : "↓" }))] }, col.key))) }) }), _jsx("tbody", { children: isLoading ? (Array.from({ length: 5 }).map((_, i) => (_jsx("tr", { className: "border-b border-neutral-100 dark:border-slate-700", children: columns.map((col) => (_jsx("td", { className: "px-4 py-3", children: _jsx(Div, { className: "h-4 w-full animate-pulse rounded bg-neutral-200 dark:bg-slate-700" }) }, col.key))) }, i)))) : rows.length === 0 ? (_jsx("tr", { children: _jsx("td", { colSpan: columns.length, className: "px-4 py-12 text-center text-neutral-500 dark:text-zinc-400", children: emptyLabel }) })) : (rows.map((row) => {
6
+ const rowHref = getRowHref?.(row);
7
+ return (_jsx("tr", { onClick: rowHref
8
+ ? () => {
9
+ window.location.href = rowHref;
10
+ }
11
+ : undefined, onKeyDown: rowHref
12
+ ? (event) => {
13
+ if (event.key === "Enter" || event.key === " ") {
14
+ window.location.href = rowHref;
15
+ }
16
+ }
17
+ : undefined, role: rowHref ? "link" : undefined, tabIndex: rowHref ? 0 : undefined, className: `border-b border-neutral-100 dark:border-slate-700 hover:bg-neutral-50 dark:hover:bg-slate-800 ${rowHref ? "cursor-pointer" : ""}`, children: columns.map((col) => (_jsx("td", { className: `px-4 py-3 text-neutral-700 dark:text-zinc-300 ${col.className ?? ""}`, children: col.render
18
+ ? col.render(row)
19
+ : String(row[col.key] ?? "") }, col.key))) }, row.id));
20
+ })) })] }) }), totalPages > 1 && onPageChange && (_jsx(Div, { className: "flex items-center justify-end gap-2 border-t border-neutral-200 dark:border-slate-700 px-4 py-3", children: Array.from({ length: totalPages }, (_, i) => i + 1).map((p) => (_jsx(Button, { onClick: () => onPageChange(p), variant: p === currentPage ? "primary" : "ghost", size: "sm", className: `h-8 w-8 rounded text-xs font-medium transition ${p === currentPage ? "bg-neutral-900 text-white" : "text-neutral-600 dark:text-zinc-300 hover:bg-neutral-100 dark:hover:bg-slate-800"}`, children: p }, p))) }))] }));
7
21
  }
@@ -1,4 +1,6 @@
1
1
  export { DataTable } from "./DataTable";
2
+ export { AdminListingScaffold } from "./AdminListingScaffold";
3
+ export type { AdminListingScaffoldRow } from "./AdminListingScaffold";
2
4
  export { DashboardStatsGrid } from "./DashboardStats";
3
5
  export { AdminUsersView } from "./AdminUsersView";
4
6
  export type { AdminUsersViewProps } from "./AdminUsersView";
@@ -58,6 +60,10 @@ export { AdminTopProductsTable } from "./analytics/AdminTopProductsTable";
58
60
  export type { AdminTopProductsTableProps, AdminTopProductsTableLabels, } from "./analytics/AdminTopProductsTable";
59
61
  export { AdminFeatureFlagsView } from "./AdminFeatureFlagsView";
60
62
  export type { AdminFeatureFlagsViewProps } from "./AdminFeatureFlagsView";
63
+ export { AdminNewsletterView } from "./AdminNewsletterView";
64
+ export type { AdminNewsletterViewProps } from "./AdminNewsletterView";
65
+ export { AdminContactView } from "./AdminContactView";
66
+ export type { AdminContactViewProps } from "./AdminContactView";
61
67
  export { AdminSidebar } from "./AdminSidebar";
62
68
  export type { AdminSidebarProps } from "./AdminSidebar";
63
69
  export { AdminTopBar } from "./AdminTopBar";
@@ -1,4 +1,5 @@
1
1
  export { DataTable } from "./DataTable";
2
+ export { AdminListingScaffold } from "./AdminListingScaffold";
2
3
  export { DashboardStatsGrid } from "./DashboardStats";
3
4
  export { AdminUsersView } from "./AdminUsersView";
4
5
  export { AdminProductsView } from "./AdminProductsView";
@@ -29,6 +30,8 @@ export { DrawerFormFooter } from "./DrawerFormFooter";
29
30
  export { AdminRevenueChart, AdminOrdersChart, } from "./analytics/AdminAnalyticsCharts";
30
31
  export { AdminTopProductsTable } from "./analytics/AdminTopProductsTable";
31
32
  export { AdminFeatureFlagsView } from "./AdminFeatureFlagsView";
33
+ export { AdminNewsletterView } from "./AdminNewsletterView";
34
+ export { AdminContactView } from "./AdminContactView";
32
35
  export { AdminSidebar } from "./AdminSidebar";
33
36
  export { AdminTopBar } from "./AdminTopBar";
34
37
  export { DemoSeedView } from "./DemoSeedView";
@@ -7,6 +7,8 @@ interface UseAdminListingDataOptions<TResponse, TRow extends {
7
7
  page?: number;
8
8
  pageSize?: number;
9
9
  sorts?: string;
10
+ filters?: string;
11
+ q?: string;
10
12
  mapRows: (response: TResponse) => TRow[];
11
13
  getTotal?: (response: TResponse, rows: TRow[]) => number;
12
14
  }
@@ -20,7 +22,7 @@ export interface UseAdminListingDataResult<TRow extends {
20
22
  }
21
23
  export declare function useAdminListingData<TResponse, TRow extends {
22
24
  id: string;
23
- }>({ queryKey, endpoint, page, pageSize, sorts, mapRows, getTotal, }: UseAdminListingDataOptions<TResponse, TRow>): UseAdminListingDataResult<TRow>;
25
+ }>({ queryKey, endpoint, page, pageSize, sorts, filters, q, mapRows, getTotal, }: UseAdminListingDataOptions<TResponse, TRow>): UseAdminListingDataResult<TRow>;
24
26
  export declare function toRecordArray(value: unknown): UnknownRecord[];
25
27
  export declare function toStringValue(value: unknown, fallback?: string): string;
26
28
  export declare function toRupees(value: unknown): string;
@@ -6,14 +6,19 @@ function withQueryParams(endpoint, params) {
6
6
  const query = new URLSearchParams(params).toString();
7
7
  return `${endpoint}${hasQuery ? "&" : "?"}${query}`;
8
8
  }
9
- export function useAdminListingData({ queryKey, endpoint, page = 1, pageSize = 25, sorts = "-createdAt", mapRows, getTotal, }) {
9
+ export function useAdminListingData({ queryKey, endpoint, page = 1, pageSize = 25, sorts = "-createdAt", filters, q, mapRows, getTotal, }) {
10
+ const params = {
11
+ page: String(page),
12
+ pageSize: String(pageSize),
13
+ sorts,
14
+ };
15
+ if (filters)
16
+ params.filters = filters;
17
+ if (q)
18
+ params.q = q;
10
19
  const query = useQuery({
11
- queryKey: [...queryKey, page, pageSize, sorts],
12
- queryFn: () => apiClient.get(withQueryParams(endpoint, {
13
- page: String(page),
14
- pageSize: String(pageSize),
15
- sorts,
16
- })),
20
+ queryKey: [...queryKey, page, pageSize, sorts, filters ?? "", q ?? ""],
21
+ queryFn: () => apiClient.get(withQueryParams(endpoint, params)),
17
22
  staleTime: 60000,
18
23
  });
19
24
  const rows = query.data ? mapRows(query.data) : [];
@@ -6,6 +6,9 @@
6
6
  export * from "./actions";
7
7
  export { notificationRepository } from "./repository/notification.repository";
8
8
  export { chatRepository } from "./repository/chat.repository";
9
+ export { contactSubmissionsRepository } from "../../core/contact-submissions.repository";
10
+ export type { ContactSubmissionDocument, ContactSubmissionCreateInput } from "../../core/contact-submissions.repository";
11
+ export { newsletterRepository } from "../../core/newsletter.repository";
9
12
  export type { ChatRoomDocument, ChatRoomCreateInput, } from "./repository/chat.repository";
10
13
  export { siteSettingsRepository } from "./repository/site-settings.repository";
11
14
  export { GET as adminProductsGET } from "./api/products/route";
@@ -6,6 +6,8 @@
6
6
  export * from "./actions";
7
7
  export { notificationRepository } from "./repository/notification.repository";
8
8
  export { chatRepository } from "./repository/chat.repository";
9
+ export { contactSubmissionsRepository } from "../../core/contact-submissions.repository";
10
+ export { newsletterRepository } from "../../core/newsletter.repository";
9
11
  export { siteSettingsRepository } from "./repository/site-settings.repository";
10
12
  export { GET as adminProductsGET } from "./api/products/route";
11
13
  export { GET as adminCouponsGET } from "./api/coupons/route";