@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
@@ -1,25 +1,84 @@
1
1
  "use client";
2
- import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useState, useCallback } from "react";
4
+ import { Search, SlidersHorizontal, LayoutGrid, List, X } from "lucide-react";
3
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
4
6
  import { useProducts } from "../../products/hooks/useProducts";
5
- import { Div, Grid, Input, Pagination, SlottedListingView, SortDropdown, } from "../../../ui";
7
+ import { Pagination, SortDropdown } from "../../../ui";
6
8
  import { ROUTES } from "../../../next";
7
9
  import { MarketplacePreorderCard } from "./MarketplacePreorderCard";
8
- export function PreOrdersIndexListing({ initialData }) {
10
+ import { PreOrderFilters } from "./PreOrderFilters";
11
+ import { useSession } from "../../../react/contexts/SessionContext";
12
+ import { useWishlistWithGuest } from "../../wishlist/hooks/useWishlistWithGuest";
13
+ import { apiClient } from "../../../http";
14
+ const PREORDER_SORT_OPTIONS = [
15
+ { value: "-createdAt", label: "Newest First" },
16
+ { value: "preOrderDeliveryDate", label: "Delivery Soon" },
17
+ { value: "price", label: "Price: Low to High" },
18
+ { value: "-price", label: "Price: High to Low" },
19
+ ];
20
+ export function PreOrdersIndexListing({ initialData, categorySlug }) {
9
21
  const table = useUrlTable({ defaults: { pageSize: "24", sort: "-createdAt" } });
22
+ const [searchInput, setSearchInput] = useState(table.get("q") || "");
23
+ const [filterOpen, setFilterOpen] = useState(false);
24
+ const [view, setView] = useState(table.get("view") || "grid");
25
+ const { user } = useSession();
26
+ const wl = useWishlistWithGuest(user?.uid ?? null);
10
27
  const params = {
11
28
  q: table.get("q") || undefined,
12
29
  category: table.get("category") || undefined,
13
- sort: table.get("sort") || undefined,
30
+ categorySlug: categorySlug || undefined,
31
+ minPrice: table.get("minPrice") ? Number(table.get("minPrice")) : undefined,
32
+ maxPrice: table.get("maxPrice") ? Number(table.get("maxPrice")) : undefined,
33
+ storeId: table.get("storeId") || undefined,
34
+ preOrderStatus: table.get("preOrderStatus") || undefined,
35
+ dateFrom: table.get("dateFrom") || undefined,
36
+ dateTo: table.get("dateTo") || undefined,
37
+ sort: table.get("sort") || "-createdAt",
14
38
  page: table.getNumber("page", 1),
15
39
  perPage: table.getNumber("pageSize", 24),
16
40
  isPreOrder: true,
17
41
  };
18
42
  const { products: preOrders, total, totalPages, page, isLoading } = useProducts(params, { initialData });
19
- return (_jsx(Div, { className: "min-h-screen", children: _jsx(SlottedListingView, { portal: "public", manageSearch: true, manageSort: true, inlineToolbar: true, renderSearch: (search, setSearch) => (_jsx(Input, { value: search, onChange: (e) => setSearch(e.target.value), placeholder: "Search pre-orders...", className: "max-w-sm" })), renderSort: (value, onChange) => (_jsx(SortDropdown, { value: value, onChange: onChange, options: [
20
- { value: "-createdAt", label: "Newest First" },
21
- { value: "preOrderDeliveryDate", label: "Delivery Soon" },
22
- { value: "-price", label: "Price: High to Low" },
23
- { value: "price", label: "Price: Low to High" },
24
- ] })), renderTable: () => (_jsx(Grid, { cols: 4, gap: "md", children: preOrders.map((product) => (_jsx(MarketplacePreorderCard, { product: product, hrefBuilder: (p) => String(ROUTES.PUBLIC.PRE_ORDER_DETAIL(p.id)) }, product.id))) })), renderPagination: () => (_jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) })), total: total, isLoading: isLoading }) }));
43
+ const commitSearch = useCallback(() => {
44
+ table.set("q", searchInput.trim());
45
+ table.setPage(1);
46
+ }, [searchInput, table]);
47
+ const handleSearchKeyDown = (e) => {
48
+ if (e.key === "Enter")
49
+ commitSearch();
50
+ };
51
+ const handleViewToggle = (next) => {
52
+ setView(next);
53
+ table.set("view", next);
54
+ };
55
+ const closeFilters = () => setFilterOpen(false);
56
+ const wishlistActions = {
57
+ addToWishlist: async (productId) => {
58
+ if (wl.isGuest)
59
+ wl.guestWishlist?.add(productId, "preorder");
60
+ else
61
+ await apiClient.post("/api/user/wishlist", { productId });
62
+ },
63
+ removeFromWishlist: async (productId) => {
64
+ if (wl.isGuest)
65
+ wl.guestWishlist?.remove(productId, "preorder");
66
+ else
67
+ await apiClient.delete(`/api/user/wishlist/${productId}`);
68
+ },
69
+ };
70
+ const handleAddToCart = useCallback(async (product) => {
71
+ try {
72
+ await apiClient.post("/api/cart", { productId: product.id, quantity: 1, isPreOrder: true });
73
+ }
74
+ catch {
75
+ // silently ignore cart errors on listing page
76
+ }
77
+ }, []);
78
+ const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4";
79
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx("div", { className: "sticky top-0 z-20 border-b border-zinc-200 dark:border-slate-700 bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm py-2.5 px-4", children: _jsxs("div", { className: "flex items-center gap-2.5 max-w-full", children: [_jsxs("button", { type: "button", onClick: () => setFilterOpen(true), className: "flex shrink-0 items-center gap-2 rounded-lg border border-zinc-300 dark:border-slate-600 px-3.5 py-2 text-sm font-medium text-zinc-700 dark:text-zinc-200 hover:bg-zinc-50 dark:hover:bg-slate-800 transition-colors", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), _jsx("span", { className: "hidden sm:inline", children: "Filters" })] }), _jsxs("div", { className: "flex flex-1 items-center overflow-hidden rounded-lg border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-900", children: [_jsx("input", { type: "text", value: searchInput, onChange: (e) => setSearchInput(e.target.value), onKeyDown: handleSearchKeyDown, placeholder: "Search pre-orders...", className: "min-w-0 flex-1 bg-transparent px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 outline-none" }), _jsx("button", { type: "button", onClick: commitSearch, className: "flex shrink-0 items-center justify-center px-3 py-2 text-zinc-400 hover:text-primary dark:hover:text-primary-400 transition-colors", "aria-label": "Search", children: _jsx(Search, { className: "h-4 w-4" }) })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1.5 text-sm text-zinc-500 dark:text-zinc-400", children: [_jsx("span", { className: "hidden md:inline whitespace-nowrap", children: "Sort by" }), _jsx(SortDropdown, { value: table.get("sort") || "-createdAt", onChange: (v) => { table.set("sort", v); table.setPage(1); }, options: PREORDER_SORT_OPTIONS })] }), _jsxs("div", { className: "flex shrink-0 items-center rounded-lg border border-zinc-300 dark:border-slate-600 overflow-hidden", children: [_jsx("button", { type: "button", onClick: () => handleViewToggle("grid"), "aria-label": "Grid view", className: `p-2 transition-colors ${view === "grid"
80
+ ? "bg-primary text-white"
81
+ : "text-zinc-500 hover:bg-zinc-50 dark:hover:bg-slate-800 dark:text-zinc-400"}`, children: _jsx(LayoutGrid, { className: "h-4 w-4" }) }), _jsx("button", { type: "button", onClick: () => handleViewToggle("list"), "aria-label": "List view", className: `p-2 transition-colors ${view === "list"
82
+ ? "bg-primary text-white"
83
+ : "text-zinc-500 hover:bg-zinc-50 dark:hover:bg-slate-800 dark:text-zinc-400"}`, children: _jsx(List, { className: "h-4 w-4" }) })] })] }) }), _jsxs("div", { className: "py-6", children: [isLoading ? (_jsx("div", { className: gridClass, children: Array.from({ length: 10 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-square bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-3 space-y-2", children: [_jsx("div", { className: "h-3 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-4 bg-zinc-200 dark:bg-slate-700 rounded w-1/3" }), _jsx("div", { className: "h-8 bg-zinc-200 dark:bg-slate-700 rounded" })] })] }, i))) })) : preOrders.length === 0 ? (_jsx("p", { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "No pre-orders found." })) : view === "list" ? (_jsx("div", { className: "flex flex-col divide-y divide-zinc-100 dark:divide-zinc-800 rounded-xl border border-zinc-100 dark:border-zinc-800", children: preOrders.map((product) => (_jsx(MarketplacePreorderCard, { product: product, variant: "list", hrefBuilder: (p) => String(ROUTES.PUBLIC.PRE_ORDER_DETAIL(p.id)), onAddToCart: handleAddToCart, wishlistActions: wishlistActions }, product.id))) })) : (_jsx("div", { className: gridClass, children: preOrders.map((product) => (_jsx(MarketplacePreorderCard, { product: product, variant: "grid", hrefBuilder: (p) => String(ROUTES.PUBLIC.PRE_ORDER_DETAIL(p.id)), onAddToCart: handleAddToCart, wishlistActions: wishlistActions }, product.id))) })), totalPages > 1 && (_jsx("div", { className: "mt-8 flex justify-center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }))] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: closeFilters }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsxs("span", { className: "flex items-center gap-2 text-base font-semibold text-zinc-900 dark:text-zinc-100", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Filters"] }), _jsx("button", { type: "button", onClick: closeFilters, "aria-label": "Close filters", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4", children: _jsx(PreOrderFilters, { table: table, currencyPrefix: "\u20B9" }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsx("button", { type: "button", onClick: closeFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors", children: "Apply filters" }) })] })] }))] }));
25
84
  }
@@ -1 +1,6 @@
1
- export declare function PreOrdersListView(): Promise<import("react/jsx-runtime").JSX.Element>;
1
+ type SearchParams = Record<string, string | string[]>;
2
+ export interface PreOrdersListViewProps {
3
+ searchParams?: SearchParams;
4
+ }
5
+ export declare function PreOrdersListView({ searchParams }: PreOrdersListViewProps): Promise<import("react/jsx-runtime").JSX.Element>;
6
+ export {};
@@ -3,14 +3,33 @@ import { productRepository } from "../../../repositories";
3
3
  import { Container, Main, Heading, Section } from "../../../ui";
4
4
  import { AdSlot } from "../../homepage/components/AdSlot";
5
5
  import { PreOrdersIndexListing } from "./PreOrdersIndexListing";
6
- export async function PreOrdersListView() {
6
+ function sp(params, key) {
7
+ const v = params[key];
8
+ return Array.isArray(v) ? v[0] ?? "" : v ?? "";
9
+ }
10
+ function buildPreOrderFilters(params) {
11
+ const parts = ["status==published", "isPreOrder==true"];
12
+ const minPrice = sp(params, "minPrice");
13
+ const maxPrice = sp(params, "maxPrice");
14
+ if (minPrice)
15
+ parts.push(`price>=${minPrice}`);
16
+ if (maxPrice)
17
+ parts.push(`price<=${maxPrice}`);
18
+ const store = sp(params, "storeId");
19
+ if (store)
20
+ parts.push(`sellerId==${store}`);
21
+ const preOrderStatus = sp(params, "preOrderStatus");
22
+ if (preOrderStatus)
23
+ parts.push(`preOrderStatus==${preOrderStatus}`);
24
+ return parts.join(",");
25
+ }
26
+ export async function PreOrdersListView({ searchParams = {} }) {
27
+ const sort = sp(searchParams, "sort") || "-createdAt";
28
+ const page = Number(sp(searchParams, "page")) || 1;
29
+ const pageSize = Number(sp(searchParams, "pageSize")) || 24;
30
+ const filters = buildPreOrderFilters(searchParams);
7
31
  const result = await productRepository
8
- .list({
9
- filters: "status==published,isPreOrder==true",
10
- sorts: "-createdAt",
11
- page: 1,
12
- pageSize: 24,
13
- })
32
+ .list({ filters, sorts: sort, page, pageSize })
14
33
  .catch(() => null);
15
34
  return (_jsx(Main, { children: _jsx(Section, { className: "py-10", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 1, className: "mb-8 text-3xl font-semibold text-zinc-900", children: "Pre-Orders" }), _jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-6" }), _jsx(PreOrdersIndexListing, { initialData: result ?? undefined }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] }) }) }));
16
35
  }
@@ -1,4 +1,6 @@
1
1
  export { PreorderBadge, PreorderCard } from "./PreorderCard";
2
+ export { PreOrderFilters } from "./PreOrderFilters";
3
+ export type { PreOrderFiltersProps } from "./PreOrderFilters";
2
4
  export { MarketplacePreorderCard, type MarketplacePreorderCardData, type MarketplacePreorderCardLabels, type MarketplacePreorderCardProps, } from "./MarketplacePreorderCard";
3
5
  export { PreOrdersListView } from "./PreOrdersListView";
4
6
  export { PreOrdersIndexListing } from "./PreOrdersIndexListing";
@@ -1,4 +1,5 @@
1
1
  export { PreorderBadge, PreorderCard } from "./PreorderCard";
2
+ export { PreOrderFilters } from "./PreOrderFilters";
2
3
  export { MarketplacePreorderCard, } from "./MarketplacePreorderCard";
3
4
  export { PreOrdersListView } from "./PreOrdersListView";
4
5
  export { PreOrdersIndexListing } from "./PreOrdersIndexListing";
@@ -1,4 +1,5 @@
1
1
  export interface AuctionsIndexListingProps {
2
2
  initialData?: any;
3
+ categorySlug?: string;
3
4
  }
4
- export declare function AuctionsIndexListing({ initialData }: AuctionsIndexListingProps): import("react/jsx-runtime").JSX.Element;
5
+ export declare function AuctionsIndexListing({ initialData, categorySlug }: AuctionsIndexListingProps): import("react/jsx-runtime").JSX.Element;
@@ -1,23 +1,75 @@
1
1
  "use client";
2
- import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useState, useCallback } from "react";
4
+ import { Search, SlidersHorizontal, LayoutGrid, List, X } from "lucide-react";
3
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
4
6
  import { useProducts } from "../hooks/useProducts";
5
- import { Div, Input, Pagination, SortDropdown, SlottedListingView, } from "../../../ui";
7
+ import { Pagination, SortDropdown } from "../../../ui";
6
8
  import { MarketplaceAuctionGrid } from "../../auctions/components/MarketplaceAuctionGrid";
7
- import { ProductFilters } from "./ProductFilters";
8
- export function AuctionsIndexListing({ initialData }) {
9
+ import { AuctionFilters } from "../../auctions/components/AuctionFilters";
10
+ import { useSession } from "../../../react/contexts/SessionContext";
11
+ import { useWishlistWithGuest } from "../../wishlist/hooks/useWishlistWithGuest";
12
+ import { apiClient } from "../../../http";
13
+ const AUCTION_SORT_OPTIONS = [
14
+ { value: "auctionEndDate", label: "Ending Soonest" },
15
+ { value: "-currentBid", label: "Highest Bid" },
16
+ { value: "-createdAt", label: "Newest" },
17
+ { value: "price", label: "Price: Low to High" },
18
+ { value: "-price", label: "Price: High to Low" },
19
+ ];
20
+ export function AuctionsIndexListing({ initialData, categorySlug }) {
9
21
  const table = useUrlTable({ defaults: { pageSize: "24", sort: "auctionEndDate" } });
22
+ const [searchInput, setSearchInput] = useState(table.get("q") || "");
23
+ const [filterOpen, setFilterOpen] = useState(false);
24
+ const [view, setView] = useState(table.get("view") || "grid");
25
+ const { user } = useSession();
26
+ const wl = useWishlistWithGuest(user?.uid ?? null);
10
27
  const params = {
11
28
  q: table.get("q") || undefined,
12
29
  category: table.get("category") || undefined,
13
- sort: table.get("sort") || undefined,
30
+ categorySlug: categorySlug || undefined,
31
+ minBid: table.get("minBid") ? Number(table.get("minBid")) : undefined,
32
+ maxBid: table.get("maxBid") ? Number(table.get("maxBid")) : undefined,
33
+ storeId: table.get("storeId") || undefined,
34
+ dateFrom: table.get("dateFrom") || undefined,
35
+ dateTo: table.get("dateTo") || undefined,
36
+ sort: table.get("sort") || "auctionEndDate",
14
37
  page: table.getNumber("page", 1),
15
38
  perPage: table.getNumber("pageSize", 24),
16
39
  isAuction: true,
17
40
  };
18
41
  const { products: auctions, total, totalPages, page, isLoading } = useProducts(params, { initialData });
19
- return (_jsx(Div, { className: "min-h-screen", children: _jsx(SlottedListingView, { portal: "public", manageSearch: true, manageSort: true, inlineToolbar: true, renderSearch: (search, setSearch) => (_jsx(Input, { value: search, onChange: (e) => setSearch(e.target.value), placeholder: "Search auctions...", className: "max-w-sm" })), renderSort: (value, onChange) => (_jsx(SortDropdown, { value: value, onChange: onChange, options: [
20
- { value: "auctionEndDate", label: "Ending Soonest" },
21
- { value: "-createdAt", label: "Newest" },
22
- ] })), renderFilters: () => _jsx(ProductFilters, { table: table }), renderTable: () => (_jsx(MarketplaceAuctionGrid, { auctions: auctions })), renderPagination: () => (_jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) })), total: total, isLoading: isLoading }) }));
42
+ const commitSearch = useCallback(() => {
43
+ table.set("q", searchInput.trim());
44
+ table.setPage(1);
45
+ }, [searchInput, table]);
46
+ const handleSearchKeyDown = (e) => {
47
+ if (e.key === "Enter")
48
+ commitSearch();
49
+ };
50
+ const handleViewToggle = (next) => {
51
+ setView(next);
52
+ table.set("view", next);
53
+ };
54
+ const closeFilters = () => setFilterOpen(false);
55
+ const wishlistActions = {
56
+ addToWishlist: async (productId) => {
57
+ if (wl.isGuest)
58
+ wl.guestWishlist?.add(productId, "auction");
59
+ else
60
+ await apiClient.post("/api/user/wishlist", { productId });
61
+ },
62
+ removeFromWishlist: async (productId) => {
63
+ if (wl.isGuest)
64
+ wl.guestWishlist?.remove(productId, "auction");
65
+ else
66
+ await apiClient.delete(`/api/user/wishlist/${productId}`);
67
+ },
68
+ };
69
+ const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-3 gap-4";
70
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx("div", { className: "sticky top-0 z-20 border-b border-zinc-200 dark:border-slate-700 bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm py-2.5 px-4", children: _jsxs("div", { className: "flex items-center gap-2.5 max-w-full", children: [_jsxs("button", { type: "button", onClick: () => setFilterOpen(true), className: "flex shrink-0 items-center gap-2 rounded-lg border border-zinc-300 dark:border-slate-600 px-3.5 py-2 text-sm font-medium text-zinc-700 dark:text-zinc-200 hover:bg-zinc-50 dark:hover:bg-slate-800 transition-colors", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), _jsx("span", { className: "hidden sm:inline", children: "Filters" })] }), _jsxs("div", { className: "flex flex-1 items-center overflow-hidden rounded-lg border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-900", children: [_jsx("input", { type: "text", value: searchInput, onChange: (e) => setSearchInput(e.target.value), onKeyDown: handleSearchKeyDown, placeholder: "Search auctions...", className: "min-w-0 flex-1 bg-transparent px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 outline-none" }), _jsx("button", { type: "button", onClick: commitSearch, className: "flex shrink-0 items-center justify-center px-3 py-2 text-zinc-400 hover:text-primary dark:hover:text-primary-400 transition-colors", "aria-label": "Search", children: _jsx(Search, { className: "h-4 w-4" }) })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1.5 text-sm text-zinc-500 dark:text-zinc-400", children: [_jsx("span", { className: "hidden md:inline whitespace-nowrap", children: "Sort by" }), _jsx(SortDropdown, { value: table.get("sort") || "auctionEndDate", onChange: (v) => { table.set("sort", v); table.setPage(1); }, options: AUCTION_SORT_OPTIONS })] }), _jsxs("div", { className: "flex shrink-0 items-center rounded-lg border border-zinc-300 dark:border-slate-600 overflow-hidden", children: [_jsx("button", { type: "button", onClick: () => handleViewToggle("grid"), "aria-label": "Grid view", className: `p-2 transition-colors ${view === "grid"
71
+ ? "bg-primary text-white"
72
+ : "text-zinc-500 hover:bg-zinc-50 dark:hover:bg-slate-800 dark:text-zinc-400"}`, children: _jsx(LayoutGrid, { className: "h-4 w-4" }) }), _jsx("button", { type: "button", onClick: () => handleViewToggle("list"), "aria-label": "List view", className: `p-2 transition-colors ${view === "list"
73
+ ? "bg-primary text-white"
74
+ : "text-zinc-500 hover:bg-zinc-50 dark:hover:bg-slate-800 dark:text-zinc-400"}`, children: _jsx(List, { className: "h-4 w-4" }) })] })] }) }), _jsxs("div", { className: "py-6", children: [isLoading ? (_jsx("div", { className: gridClass, children: Array.from({ length: 8 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-square bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-3 space-y-2", children: [_jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-full" }), _jsx("div", { className: "h-8 bg-zinc-200 dark:bg-slate-700 rounded" })] })] }, i))) })) : (_jsx(MarketplaceAuctionGrid, { auctions: auctions, variant: view === "list" ? "list" : "grid", gridClassName: view === "list" ? "flex flex-col gap-4" : gridClass, wishlistActions: wishlistActions })), totalPages > 1 && (_jsx("div", { className: "mt-8 flex justify-center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }))] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: closeFilters }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsxs("span", { className: "flex items-center gap-2 text-base font-semibold text-zinc-900 dark:text-zinc-100", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Filters"] }), _jsx("button", { type: "button", onClick: closeFilters, "aria-label": "Close filters", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4", children: _jsx(AuctionFilters, { table: table, currencyPrefix: "\u20B9" }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsx("button", { type: "button", onClick: closeFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors", children: "Apply filters" }) })] })] }))] }));
23
75
  }
@@ -9,11 +9,9 @@ export interface InteractiveProductCardProps {
9
9
  selectable?: boolean;
10
10
  isSelected?: boolean;
11
11
  onSelect?: (id: string, selected: boolean) => void;
12
- /** Whether this product is currently in the user's wishlist */
13
12
  isWishlisted?: boolean;
14
- /** Called when the wishlist icon is toggled */
15
13
  onToggleWishlist?: (productId: string) => void;
16
- /** Called when the card body is clicked in non-selectable mode */
17
14
  onAddToCart?: (product: ProductItem) => void;
15
+ onBuyNow?: (product: ProductItem) => void;
18
16
  }
19
- export declare function InteractiveProductCard({ product, href, className, selectable, isSelected, onSelect, isWishlisted, onToggleWishlist, onAddToCart, }: InteractiveProductCardProps): import("react/jsx-runtime").JSX.Element;
17
+ export declare function InteractiveProductCard({ product, href, className, selectable, isSelected, onSelect, isWishlisted, onToggleWishlist, onAddToCart, onBuyNow, }: InteractiveProductCardProps): import("react/jsx-runtime").JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import Link from "next/link";
3
3
  import { ProductCard } from "./ProductGrid";
4
- export function InteractiveProductCard({ product, href, className, selectable, isSelected, onSelect, isWishlisted = false, onToggleWishlist, onAddToCart, }) {
4
+ export function InteractiveProductCard({ product, href, className, selectable, isSelected, onSelect, isWishlisted = false, onToggleWishlist, onAddToCart, onBuyNow, }) {
5
5
  return (_jsx(Link, { href: href, className: [
6
6
  "block",
7
7
  selectable ? "cursor-pointer" : "",
@@ -13,5 +13,5 @@ export function InteractiveProductCard({ product, href, className, selectable, i
13
13
  e.preventDefault();
14
14
  onSelect(product.id, !isSelected);
15
15
  }
16
- : undefined, children: _jsx(ProductCard, { product: product, className: className, isWishlisted: isWishlisted, onAddToWishlist: onToggleWishlist, onClick: !selectable && onAddToCart ? () => onAddToCart(product) : undefined }) }));
16
+ : undefined, children: _jsx(ProductCard, { product: product, className: className, isWishlisted: isWishlisted, onAddToWishlist: onToggleWishlist, onAddToCart: onAddToCart, onBuyNow: onBuyNow }) }));
17
17
  }
@@ -1,4 +1,4 @@
1
1
  export interface ProductDetailPageViewProps {
2
2
  slug: string;
3
3
  }
4
- export declare function ProductDetailPageView({ slug }: ProductDetailPageViewProps): Promise<import("react/jsx-runtime").JSX.Element>;
4
+ export declare function ProductDetailPageView({ slug, }: ProductDetailPageViewProps): Promise<import("react/jsx-runtime").JSX.Element>;
@@ -5,25 +5,28 @@ import { ROUTES } from "../../../next";
5
5
  import { getDefaultCurrency } from "../../../core/baseline-resolver";
6
6
  import { Button, Container, Div, Heading, Main, RichText, Row, Section, Span, Stack, Text, } from "../../../ui";
7
7
  import { normalizeRichTextHtml } from "../../../utils/string.formatter";
8
+ import { formatCurrency } from "../../../utils/number.formatter";
9
+ import { safeDisplayName } from "../../../security";
8
10
  import { ReviewsList } from "../../reviews/components/ReviewsList";
9
- import { ProductGrid } from "./ProductGrid";
10
- import { RelatedProducts } from "./RelatedProducts";
11
- import { BuyBar } from "./BuyBar";
12
11
  import { ProductDetailView } from "./ProductDetailView";
13
12
  import { ProductGalleryClient } from "./ProductGalleryClient";
14
13
  import { ProductTabsShell } from "./ProductTabsShell";
14
+ import { ProductFeatureBadges } from "./ProductFeatureBadges";
15
+ import { RelatedProductsCarousel } from "./RelatedProductsCarousel";
16
+ import { BuyBar } from "./BuyBar";
15
17
  // ---------------------------------------------------------------------------
16
18
  // Helpers
17
19
  // ---------------------------------------------------------------------------
18
- function mapToProductItem(doc) {
20
+ function toProductItem(doc) {
19
21
  return {
20
22
  id: String(doc.id ?? ""),
21
- title: String(doc.name ?? doc.title ?? "Product"),
23
+ title: String(doc.title ?? doc.name ?? ""),
22
24
  price: typeof doc.price === "number" ? doc.price : 0,
25
+ originalPrice: typeof doc.originalPrice === "number" ? doc.originalPrice : undefined,
23
26
  mainImage: Array.isArray(doc.images)
24
27
  ? doc.images[0]
25
- : typeof doc.imageUrl === "string"
26
- ? doc.imageUrl
28
+ : typeof doc.mainImage === "string"
29
+ ? doc.mainImage
27
30
  : undefined,
28
31
  status: doc.status ?? "published",
29
32
  slug: typeof doc.slug === "string" ? doc.slug : undefined,
@@ -32,7 +35,7 @@ function mapToProductItem(doc) {
32
35
  reviewCount: typeof doc.reviewCount === "number" ? doc.reviewCount : undefined,
33
36
  };
34
37
  }
35
- function mapToReview(doc) {
38
+ function toReview(doc) {
36
39
  const images = Array.isArray(doc.images)
37
40
  ? doc.images.map((url) => ({ url }))
38
41
  : undefined;
@@ -60,41 +63,129 @@ function mapToReview(doc) {
60
63
  createdAt,
61
64
  };
62
65
  }
66
+ function toDescriptionHtml(raw) {
67
+ if (!raw)
68
+ return "";
69
+ const s = typeof raw === "string" ? raw : JSON.stringify(raw);
70
+ return normalizeRichTextHtml(s);
71
+ }
72
+ function StarRating({ value }) {
73
+ const full = Math.floor(value);
74
+ const half = value - full >= 0.5;
75
+ return (_jsx(Row, { align: "center", gap: "xs", children: Array.from({ length: 5 }).map((_, i) => (_jsx(Span, { className: i < full
76
+ ? "text-yellow-400 text-sm"
77
+ : i === full && half
78
+ ? "text-yellow-300 text-sm"
79
+ : "text-zinc-300 dark:text-zinc-600 text-sm", children: "\u2605" }, i))) }));
80
+ }
63
81
  // ---------------------------------------------------------------------------
64
82
  // Component
65
83
  // ---------------------------------------------------------------------------
66
- export async function ProductDetailPageView({ slug }) {
67
- const product = await productRepository.findByIdOrSlug(slug).catch(() => undefined);
84
+ export async function ProductDetailPageView({ slug, }) {
85
+ const product = await productRepository
86
+ .findByIdOrSlug(slug)
87
+ .catch(() => undefined);
68
88
  if (!product) {
69
89
  return (_jsx(Main, { children: _jsx(Section, { className: "py-20", children: _jsx(Container, { size: "md", children: _jsxs(Stack, { align: "center", gap: "md", className: "text-center", children: [_jsx(Heading, { level: 1, className: "text-2xl font-semibold text-zinc-900 dark:text-zinc-50", children: "Product Not Found" }), _jsx(Text, { className: "text-zinc-500", children: "The product you are looking for may have been removed or the link is incorrect." }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRODUCTS), className: "text-sm font-medium text-primary-600 hover:underline", children: "Browse Products" })] }) }) }) }));
70
90
  }
71
91
  const p = product;
72
92
  const currency = p.currency || getDefaultCurrency();
73
- const price = typeof p.price === "number"
74
- ? new Intl.NumberFormat(undefined, { style: "currency", currency }).format(p.price)
93
+ // -- Derived values ---------------------------------------------------------
94
+ const title = String(p.title ?? p.name ?? "");
95
+ const price = typeof p.price === "number" ? p.price : null;
96
+ const originalPrice = typeof p.originalPrice === "number" ? p.originalPrice : null;
97
+ const discount = price && originalPrice && originalPrice > price
98
+ ? Math.round(((originalPrice - price) / originalPrice) * 100)
75
99
  : null;
76
100
  const images = Array.isArray(p.images)
77
101
  ? p.images
78
- : typeof p.imageUrl === "string"
79
- ? [p.imageUrl]
102
+ : typeof p.mainImage === "string"
103
+ ? [p.mainImage]
80
104
  : [];
81
- const inStock = typeof p.stockQuantity === "number" ? p.stockQuantity > 0 : p.status === "published";
105
+ const stockQuantity = typeof p.stockQuantity === "number" ? p.stockQuantity : null;
106
+ const availableQuantity = typeof p.availableQuantity === "number"
107
+ ? p.availableQuantity
108
+ : null;
109
+ const effectiveStock = availableQuantity ?? stockQuantity;
110
+ const inStock = effectiveStock !== null ? effectiveStock > 0 : p.status === "published";
111
+ const avgRating = typeof p.avgRating === "number" ? p.avgRating : null;
112
+ const reviewCount = typeof p.reviewCount === "number" ? p.reviewCount : null;
113
+ const category = typeof p.category === "string" ? p.category : null;
114
+ const subcategory = typeof p.subcategory === "string" ? p.subcategory : null;
115
+ const brand = typeof p.brand === "string" ? p.brand : null;
116
+ const condition = typeof p.condition === "string" ? p.condition : null;
117
+ const tags = Array.isArray(p.tags) ? p.tags : [];
118
+ const features = Array.isArray(p.features)
119
+ ? p.features
120
+ : [];
121
+ const ingredients = Array.isArray(p.ingredients)
122
+ ? p.ingredients
123
+ : [];
124
+ const howToUse = Array.isArray(p.howToUse)
125
+ ? p.howToUse
126
+ : [];
82
127
  const specs = Array.isArray(p.specifications)
83
128
  ? p.specifications
84
129
  : [];
85
- // Fetch reviews and related products in parallel
130
+ const shippingInfo = typeof p.shippingInfo === "string" ? p.shippingInfo : null;
131
+ const returnPolicy = typeof p.returnPolicy === "string" ? p.returnPolicy : null;
132
+ const shippingPaidBy = p.shippingPaidBy;
133
+ const freeShipping = shippingPaidBy === "seller";
134
+ const featured = p.featured === true;
135
+ const sellerName = typeof p.sellerName === "string" ? p.sellerName : null;
136
+ const safeSeller = sellerName
137
+ ? safeDisplayName(sellerName, "")
138
+ : null;
139
+ const descriptionHtml = toDescriptionHtml(p.description);
140
+ // -- Fetch reviews + related in parallel ------------------------------------
86
141
  const [reviewDocs, relatedDocs] = await Promise.all([
87
- reviewRepository.findApprovedByProduct(product.id).catch(() => []),
88
- typeof p.category === "string" && p.category
89
- ? productRepository.findByCategory(p.category).catch(() => [])
142
+ reviewRepository
143
+ .findApprovedByProduct(product.id)
144
+ .catch(() => []),
145
+ category
146
+ ? productRepository.findByCategory(category).catch(() => [])
90
147
  : Promise.resolve([]),
91
148
  ]);
92
- const reviews = reviewDocs.map((doc) => mapToReview(doc));
149
+ const reviews = reviewDocs.map(toReview);
93
150
  const relatedItems = relatedDocs
94
151
  .filter((r) => r.id !== product.id)
95
- .slice(0, 4)
96
- .map(mapToProductItem);
97
- return (_jsxs(_Fragment, { children: [_jsx(ProductDetailView, { renderGallery: () => (_jsx(ProductGalleryClient, { images: images, productName: typeof p.name === "string" ? p.name : undefined })), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 1, className: "text-2xl font-bold text-zinc-900 dark:text-zinc-50", children: typeof p.name === "string" ? p.name : "Product" }), price && (_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(Span, { className: "text-2xl font-semibold text-primary-600 dark:text-primary-400", children: price }), !inStock && (_jsx(Span, { className: "rounded-full bg-red-100 px-2.5 py-0.5 text-xs font-medium text-red-700 dark:bg-red-900/30 dark:text-red-400", children: "Out of stock" }))] })), typeof p.description === "string" && p.description && (_jsx(RichText, { html: normalizeRichTextHtml(p.description), className: "text-sm leading-relaxed text-zinc-600 dark:text-zinc-400" })), typeof p.sellerName === "string" && p.sellerName && (_jsxs(Row, { align: "center", gap: "xs", children: [_jsx(Span, { className: "text-xs text-zinc-500", children: "Sold by" }), _jsx(Span, { className: "text-xs font-medium text-zinc-700 dark:text-zinc-300", children: p.sellerName })] }))] })), renderActions: () => (_jsx(Div, { className: "rounded-xl border border-zinc-100 bg-zinc-50 p-5 dark:border-zinc-800 dark:bg-zinc-900", children: _jsxs(Stack, { gap: "sm", children: [price && (_jsx(Text, { className: "text-xl font-bold text-zinc-900 dark:text-zinc-50", children: price })), _jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: !inStock, children: inStock ? "Add to Cart" : "Out of Stock" }), _jsx(Button, { variant: "secondary", size: "md", className: "w-full", children: "Add to Wishlist" })] }) })), renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: typeof p.description === "string" && p.description ? (_jsx(RichText, { html: normalizeRichTextHtml(p.description), className: "text-sm leading-relaxed text-zinc-600 dark:text-zinc-400" })) : undefined, specsContent: specs.length > 0 ? (_jsx("dl", { className: "divide-y divide-zinc-100 dark:divide-zinc-800 rounded-xl border border-zinc-100 dark:border-zinc-800 overflow-hidden text-sm", children: specs.map((s, i) => (_jsxs("div", { className: "flex gap-4 px-4 py-3 bg-white dark:bg-zinc-900 even:bg-zinc-50 dark:even:bg-zinc-800/50", children: [_jsx("dt", { className: "w-40 flex-shrink-0 font-medium text-zinc-700 dark:text-zinc-300", children: s.name }), _jsxs("dd", { className: "flex-1 text-zinc-600 dark:text-zinc-400", children: [s.value, s.unit ? ` ${s.unit}` : ""] })] }, i))) })) : undefined, reviewsContent: _jsx(ReviewsList, { reviews: reviews, emptyLabel: "No reviews yet \u2014 be the first to review this product." }) })), renderRelated: () => relatedItems.length > 0 ? (_jsx(RelatedProducts, { labels: { title: "You might also like" }, renderGrid: () => (_jsx(ProductGrid, { products: relatedItems, getProductHref: (item) => String(ROUTES.PUBLIC.PRODUCT_DETAIL(item.slug
98
- ? String(item.slug)
99
- : item.id)) })) })) : null }), _jsxs(BuyBar, { children: [price && (_jsx(Span, { className: "mr-auto text-sm font-bold text-zinc-900 dark:text-zinc-50", children: price })), _jsx(Button, { variant: "primary", size: "sm", className: "flex-1", disabled: !inStock, children: inStock ? "Add to Cart" : "Out of Stock" })] })] }));
152
+ .slice(0, 8)
153
+ .map(toProductItem);
154
+ const formattedPrice = price !== null ? formatCurrency(price, currency) : null;
155
+ const formattedOriginal = originalPrice !== null ? formatCurrency(originalPrice, currency) : null;
156
+ return (_jsx(Main, { children: _jsxs(Container, { size: "xl", className: "px-4 py-6", children: [_jsx(ProductDetailView, { renderBreadcrumb: () => (_jsxs("nav", { "aria-label": "Breadcrumb", className: "mb-4 flex items-center gap-1.5 text-xs text-zinc-500 dark:text-zinc-400 flex-wrap", children: [_jsx(Link, { href: String(ROUTES.HOME), className: "hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRODUCTS), className: "hover:text-primary-600 transition-colors", children: "Products" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "capitalize", children: category })] })), subcategory && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "capitalize", children: subcategory })] }))] })), renderGallery: () => (_jsx(ProductGalleryClient, { images: images, productName: title || undefined })), renderInfo: () => (_jsxs(Stack, { gap: "sm", children: [_jsxs(Div, { children: [condition && (_jsx(Span, { className: "mb-1.5 inline-block rounded-full bg-zinc-100 dark:bg-zinc-800 px-2.5 py-0.5 text-xs font-medium capitalize text-zinc-600 dark:text-zinc-300", children: condition === "new"
157
+ ? "Brand New"
158
+ : condition === "like_new"
159
+ ? "Like New"
160
+ : condition === "refurbished"
161
+ ? "Refurbished"
162
+ : condition === "used"
163
+ ? "Used"
164
+ : condition })), _jsx(Heading, { level: 1, className: "text-xl font-bold leading-snug text-zinc-900 dark:text-zinc-50 sm:text-2xl", children: title || "Untitled Product" })] }), avgRating !== null && (_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(StarRating, { value: avgRating }), _jsxs(Span, { className: "text-sm text-zinc-600 dark:text-zinc-400", children: [avgRating.toFixed(1), reviewCount ? ` (${reviewCount} reviews)` : ""] }), _jsxs(Span, { className: `ml-auto rounded-full px-2.5 py-0.5 text-xs font-medium ${inStock
165
+ ? "bg-emerald-50 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400"
166
+ : "bg-red-50 text-red-700 dark:bg-red-900/30 dark:text-red-400"}`, children: [inStock ? "✓ In Stock" : "✗ Out of Stock", inStock && effectiveStock !== null && effectiveStock <= 10
167
+ ? ` — only ${effectiveStock} left`
168
+ : ""] })] })), formattedPrice && (_jsxs(Row, { align: "baseline", gap: "sm", children: [_jsx(Span, { className: "text-2xl font-bold text-zinc-900 dark:text-zinc-50", children: formattedPrice }), formattedOriginal && discount && (_jsxs(_Fragment, { children: [_jsx(Span, { className: "text-sm text-zinc-400 line-through dark:text-zinc-500", children: formattedOriginal }), _jsxs(Span, { className: "rounded-full bg-red-500 px-2 py-0.5 text-xs font-bold text-white", children: ["-", discount, "%"] })] }))] })), avgRating === null && (_jsxs(Span, { className: `inline-flex w-fit items-center gap-1.5 rounded-full px-3 py-1 text-xs font-medium ${inStock
169
+ ? "bg-emerald-50 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400"
170
+ : "bg-red-50 text-red-700 dark:bg-red-900/30 dark:text-red-400"}`, children: [inStock ? "✓ In Stock" : "✗ Out of Stock", inStock && effectiveStock !== null && effectiveStock <= 10
171
+ ? ` — only ${effectiveStock} left`
172
+ : ""] })), (category || brand) && (_jsxs(Row, { align: "center", gap: "xs", className: "text-xs text-zinc-400 dark:text-zinc-500 flex-wrap", children: [category && _jsx(Span, { className: "capitalize", children: category }), category && brand && _jsx(Span, { children: "\u203A" }), brand && _jsx(Span, { className: "font-medium text-zinc-600 dark:text-zinc-300", children: brand })] })), _jsx(ProductFeatureBadges, { featured: featured, freeShipping: freeShipping, condition: condition ?? undefined, returnable: returnPolicy != null && returnPolicy.length > 0, labels: {
173
+ featured: "Featured",
174
+ fasterDelivery: "Faster Delivery",
175
+ ratedSeller: "Rated Seller",
176
+ condition: "Condition",
177
+ conditionNew: "New",
178
+ conditionUsed: "Used",
179
+ conditionBroken: "For Parts",
180
+ conditionRefurbished: "Refurbished",
181
+ returnable: "Returnable",
182
+ freeShipping: "Free Shipping",
183
+ codAvailable: "Cash on Delivery",
184
+ wishlistCount: (n) => `${n} wishlisted`,
185
+ categoryProductCount: (n, cat) => `${n} in ${cat}`,
186
+ } }), features.length > 0 && (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 px-4 py-3", children: [_jsx(Text, { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-400", children: "About this product" }), _jsx("ul", { className: "space-y-1.5", children: features.map((f, i) => (_jsxs("li", { className: "flex items-start gap-2 text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i))) })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-sm leading-relaxed text-zinc-600 dark:text-zinc-400 line-clamp-4" })), safeSeller && (_jsxs(Row, { align: "center", gap: "xs", className: "border-t border-zinc-100 dark:border-zinc-800 pt-3", children: [_jsx(Span, { className: "text-xs text-zinc-500", children: "Sold by" }), _jsx(Span, { className: "text-xs font-medium text-zinc-700 dark:text-zinc-300", children: safeSeller })] }))] })), renderActions: () => (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-5 space-y-4", children: [formattedPrice && (_jsxs(Div, { children: [_jsx(Text, { className: "text-2xl font-bold text-zinc-900 dark:text-zinc-50", children: formattedPrice }), inStock && effectiveStock !== null && effectiveStock <= 10 && (_jsxs(Text, { className: "mt-0.5 text-xs text-amber-600 dark:text-amber-400", children: ["Only ", effectiveStock, " left \u2014 order soon!"] }))] })), _jsxs(Stack, { gap: "sm", children: [_jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: !inStock, children: inStock ? "Add to Cart" : "Out of Stock" }), _jsx(Button, { variant: "secondary", size: "md", className: "w-full", children: "\u2661 Add to Wishlist" })] }), (shippingInfo || returnPolicy || freeShipping) && (_jsxs(Div, { className: "border-t border-zinc-200 dark:border-zinc-700 pt-4 space-y-2.5", children: [freeShipping && (_jsxs(Row, { align: "start", gap: "sm", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-emerald-500", children: "\uD83D\uDE9A" }), _jsxs(Div, { children: [_jsx(Text, { className: "text-xs font-medium text-zinc-700 dark:text-zinc-300", children: "Free Delivery" }), shippingInfo && (_jsx(Text, { className: "text-xs text-zinc-500", children: shippingInfo }))] })] })), !freeShipping && shippingInfo && (_jsxs(Row, { align: "start", gap: "sm", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-zinc-400", children: "\uD83D\uDCE6" }), _jsx(Text, { className: "text-xs text-zinc-600 dark:text-zinc-400", children: shippingInfo })] })), returnPolicy && (_jsxs(Row, { align: "start", gap: "sm", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-zinc-400", children: "\u21BA" }), _jsx(Text, { className: "text-xs text-zinc-600 dark:text-zinc-400", children: returnPolicy })] }))] })), tags.length > 0 && (_jsxs(Div, { className: "border-t border-zinc-200 dark:border-zinc-700 pt-4", children: [_jsx(Text, { className: "mb-2 text-xs font-medium text-zinc-500 dark:text-zinc-400", children: "Tags" }), _jsx(Row, { wrap: true, gap: "xs", children: tags.map((tag) => (_jsx(Span, { className: "rounded-full bg-zinc-100 dark:bg-zinc-800 px-2.5 py-1 text-xs text-zinc-600 dark:text-zinc-300", children: tag }, tag))) })] })), _jsx(Div, { className: "border-t border-zinc-200 dark:border-zinc-700 pt-4", children: _jsx(Row, { wrap: true, gap: "sm", className: "justify-center text-center", children: [
187
+ { icon: "🔒", label: "Secure\nPayment" },
188
+ { icon: "✓", label: "Verified\nSeller" },
189
+ { icon: "⭐", label: "Quality\nGuarantee" },
190
+ ].map(({ icon, label }) => (_jsxs(Div, { className: "flex flex-col items-center gap-1 text-xs text-zinc-500 dark:text-zinc-400 min-w-[60px]", children: [_jsx(Span, { className: "text-base", children: icon }), _jsx(Span, { className: "whitespace-pre-line leading-tight", children: label })] }, label))) }) })] })), renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-zinc-700 dark:text-zinc-300" })) : undefined, specsContent: specs.length > 0 ? (_jsx("dl", { className: "divide-y divide-zinc-100 dark:divide-zinc-800 rounded-xl border border-zinc-100 dark:border-zinc-800 overflow-hidden text-sm", children: specs.map((s, i) => (_jsxs("div", { className: "flex gap-4 px-4 py-3 bg-white dark:bg-zinc-900 even:bg-zinc-50 dark:even:bg-zinc-800/50", children: [_jsx("dt", { className: "w-36 flex-shrink-0 font-medium text-zinc-700 dark:text-zinc-300", children: s.name }), _jsxs("dd", { className: "flex-1 text-zinc-600 dark:text-zinc-400", children: [s.value, s.unit ? ` ${s.unit}` : ""] })] }, i))) })) : undefined, ingredientsContent: ingredients.length > 0 ? (_jsx("ul", { className: "space-y-2", children: ingredients.map((item, i) => (_jsxs("li", { className: "flex items-start gap-2 text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx(Span, { className: "mt-1 flex-shrink-0 h-1.5 w-1.5 rounded-full bg-primary-400" }), item] }, i))) })) : undefined, howToUseContent: howToUse.length > 0 ? (_jsx("ol", { className: "space-y-3", children: howToUse.map((step, i) => (_jsxs("li", { className: "flex items-start gap-3 text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx(Span, { className: "flex-shrink-0 flex h-6 w-6 items-center justify-center rounded-full bg-primary-100 dark:bg-primary-900/30 text-xs font-bold text-primary-700 dark:text-primary-300", children: i + 1 }), step] }, i))) })) : undefined, reviewsContent: _jsx(ReviewsList, { reviews: reviews, emptyLabel: "No reviews yet \u2014 be the first to review this product." }) })), renderRelated: () => relatedItems.length > 0 ? (_jsx(RelatedProductsCarousel, { items: relatedItems })) : null }), _jsxs(BuyBar, { children: [formattedPrice && (_jsx(Span, { className: "mr-auto text-sm font-bold text-zinc-900 dark:text-zinc-50", children: formattedPrice })), _jsx(Button, { variant: "primary", size: "sm", className: "flex-1", disabled: !inStock, children: inStock ? "Add to Cart" : "Out of Stock" })] })] }) }));
100
191
  }
@@ -68,23 +68,18 @@ export declare function getProductSortOptions(variant: ProductFilterVariant): Re
68
68
  }>;
69
69
  export interface ProductFiltersProps {
70
70
  table: UrlTable;
71
- /** Pass category options loaded from the API */
72
71
  categoryOptions?: FacetOption[];
73
- /** Pass brand options loaded from the API */
74
72
  brandOptions?: FacetOption[];
75
- /** Pass seller name options */
73
+ /** Store options (formerly sellerOptions) */
74
+ storeOptions?: FacetOption[];
75
+ /** @deprecated use storeOptions */
76
76
  sellerOptions?: FacetOption[];
77
- /** Override defaults with dynamic tag options */
78
77
  tagOptions?: FacetOption[];
79
- /** Show status filter (admin / seller only) */
80
78
  showStatus?: boolean;
81
- /** Preferred variant contract (overrides showStatus defaults). */
82
79
  variant?: ProductFilterVariant;
83
80
  statusOptions?: FacetOption[];
84
- /**
85
- * Currency prefix shown in price range labels (e.g. "₹", "$", "€").
86
- * Injected by the consumer app — never hard-coded in appkit.
87
- */
88
81
  currencyPrefix?: string;
82
+ /** Show free-shipping toggle */
83
+ showShipping?: boolean;
89
84
  }
90
- export declare function ProductFilters({ table, categoryOptions, brandOptions, sellerOptions, tagOptions, showStatus, variant, statusOptions, currencyPrefix, }: ProductFiltersProps): import("react/jsx-runtime").JSX.Element;
85
+ export declare function ProductFilters({ table, categoryOptions, brandOptions, storeOptions, sellerOptions, tagOptions, showStatus, variant, statusOptions, currencyPrefix, showShipping, }: ProductFiltersProps): import("react/jsx-runtime").JSX.Element;
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useTranslations } from "next-intl";
3
3
  import { FilterFacetSection } from "../../filters/FilterFacetSection";
4
4
  import { RangeFilter } from "../../filters/RangeFilter";
5
+ import { SwitchFilter } from "../../filters/SwitchFilter";
5
6
  import { Div } from "../../../ui";
6
7
  export const PRODUCT_FILTER_KEYS = {
7
8
  admin: [
@@ -71,7 +72,8 @@ export function getProductSortOptions(variant) {
71
72
  return PRODUCT_PUBLIC_SORT_OPTIONS;
72
73
  }
73
74
  }
74
- export function ProductFilters({ table, categoryOptions = [], brandOptions = [], sellerOptions = [], tagOptions = [], showStatus = false, variant, statusOptions, currencyPrefix = "", }) {
75
+ export function ProductFilters({ table, categoryOptions = [], brandOptions = [], storeOptions = [], sellerOptions, tagOptions = [], showStatus = false, variant, statusOptions, currencyPrefix = "", showShipping = true, }) {
76
+ const resolvedStoreOptions = storeOptions.length > 0 ? storeOptions : (sellerOptions ?? []);
75
77
  const t = useTranslations("filters");
76
78
  const conditionOptions = [
77
79
  { value: "new", label: t("conditionNew") },
@@ -88,7 +90,7 @@ export function ProductFilters({ table, categoryOptions = [], brandOptions = [],
88
90
  ? table.get("category").split("|").filter(Boolean)
89
91
  : [];
90
92
  const selectedBrands = table.get("brand") ? [table.get("brand")] : [];
91
- const selectedSellers = table.get("seller") ? [table.get("seller")] : [];
93
+ const selectedSellers = table.get("storeId") ? [table.get("storeId")] : [];
92
94
  const selectedConditions = table.get("condition")
93
95
  ? table.get("condition").split("|").filter(Boolean)
94
96
  : [];
@@ -100,5 +102,5 @@ export function ProductFilters({ table, categoryOptions = [], brandOptions = [],
100
102
  : [];
101
103
  const resolvedVariant = variant ?? (showStatus ? "admin" : "public");
102
104
  const shouldShowStatus = resolvedVariant !== "public" || showStatus;
103
- return (_jsxs(Div, { children: [categoryOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("category"), options: categoryOptions, selected: selectedCategories, onChange: (vals) => table.set("category", vals.join("|")), searchable: categoryOptions.length > 6, defaultCollapsed: true })), _jsx(FilterFacetSection, { title: t("condition"), options: conditionOptions, selected: selectedConditions, onChange: (vals) => table.set("condition", vals.join("|")), searchable: false, defaultCollapsed: true }), _jsx(RangeFilter, { title: t("priceRange"), minValue: table.get("minPrice"), maxValue: table.get("maxPrice"), onMinChange: (v) => table.set("minPrice", v), onMaxChange: (v) => table.set("maxPrice", v), prefix: currencyPrefix, showSlider: true, minBound: 0, maxBound: 500000, step: 500, minPlaceholder: t("minPrice"), maxPlaceholder: t("maxPrice"), defaultCollapsed: true }), brandOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("brand"), options: brandOptions, selected: selectedBrands, onChange: (vals) => table.set("brand", vals[0] ?? ""), searchable: brandOptions.length > 6, defaultCollapsed: true })), sellerOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("seller"), options: sellerOptions, selected: selectedSellers, onChange: (vals) => table.set("seller", vals[0] ?? ""), searchable: sellerOptions.length > 6, defaultCollapsed: true })), tagOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("tags"), options: tagOptions, selected: selectedTags, onChange: (vals) => table.set("tags", vals.join("|")), searchable: tagOptions.length > 6, defaultCollapsed: true })), shouldShowStatus && (_jsx(FilterFacetSection, { title: t("status"), options: statusOptions ?? defaultStatusOptions, selected: selectedStatuses, onChange: (vals) => table.set("status", vals.join("|")), searchable: false, defaultCollapsed: true }))] }));
105
+ return (_jsxs(Div, { children: [categoryOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("category"), options: categoryOptions, selected: selectedCategories, onChange: (vals) => table.set("category", vals.join("|")), searchable: categoryOptions.length > 6, defaultCollapsed: true })), _jsx(FilterFacetSection, { title: t("condition"), options: conditionOptions, selected: selectedConditions, onChange: (vals) => table.set("condition", vals.join("|")), searchable: false, defaultCollapsed: true }), _jsx(RangeFilter, { title: t("priceRange"), minValue: table.get("minPrice"), maxValue: table.get("maxPrice"), onMinChange: (v) => table.set("minPrice", v), onMaxChange: (v) => table.set("maxPrice", v), prefix: currencyPrefix, showSlider: true, minBound: 0, maxBound: 500000, step: 500, minPlaceholder: t("minPrice"), maxPlaceholder: t("maxPrice"), defaultCollapsed: true }), brandOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("brand"), options: brandOptions, selected: selectedBrands, onChange: (vals) => table.set("brand", vals[0] ?? ""), searchable: brandOptions.length > 6, defaultCollapsed: true })), resolvedStoreOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("store"), options: resolvedStoreOptions, selected: selectedSellers, onChange: (vals) => table.set("storeId", vals[0] ?? ""), searchable: resolvedStoreOptions.length > 6, defaultCollapsed: true })), showShipping && (_jsx(SwitchFilter, { title: t("shipping"), label: t("freeShippingOnly"), checked: table.get("freeShipping") === "true", onChange: (v) => table.set("freeShipping", v ? "true" : ""), defaultCollapsed: true })), tagOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("tags"), options: tagOptions, selected: selectedTags, onChange: (vals) => table.set("tags", vals.join("|")), searchable: tagOptions.length > 6, defaultCollapsed: true })), shouldShowStatus && (_jsx(FilterFacetSection, { title: t("status"), options: statusOptions ?? defaultStatusOptions, selected: selectedStatuses, onChange: (vals) => table.set("status", vals.join("|")), searchable: false, defaultCollapsed: true }))] }));
104
106
  }