@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
@@ -4,68 +4,114 @@ import { productRepository } from "../../../repositories";
4
4
  import { listBidsByProduct } from "../../auctions/actions/bid-actions";
5
5
  import { ROUTES } from "../../../next";
6
6
  import { getDefaultCurrency } from "../../../core/baseline-resolver";
7
- import { Button, Container, Div, Heading, Input, Main, Row, Section, Span, Stack, Text, } from "../../../ui";
7
+ import { formatCurrency } from "../../../utils/number.formatter";
8
+ import { normalizeRichTextHtml } from "../../../utils/string.formatter";
9
+ import { safeDisplayName } from "../../../security";
10
+ import { Button, Container, Div, Heading, Input, Main, RichText, Row, Section, Span, Stack, Text, } from "../../../ui";
8
11
  import { AuctionDetailView } from "../../products/components/AuctionDetailView";
9
12
  import { BidHistory } from "../../products/components/BidHistory";
13
+ import { BuyBar } from "../../products/components/BuyBar";
10
14
  import { RelatedProducts } from "../../products/components/RelatedProducts";
15
+ import { ProductGalleryClient } from "../../products/components/ProductGalleryClient";
16
+ import { ProductFeatureBadges } from "../../products/components/ProductFeatureBadges";
11
17
  import { MarketplaceAuctionGrid } from "./MarketplaceAuctionGrid";
18
+ function toDescriptionHtml(raw) {
19
+ if (!raw)
20
+ return "";
21
+ const s = typeof raw === "string" ? raw : JSON.stringify(raw);
22
+ return normalizeRichTextHtml(s);
23
+ }
12
24
  export async function AuctionDetailPageView({ id }) {
13
25
  const [product, bidsResult] = await Promise.all([
14
26
  productRepository.findByIdOrSlug(id).catch(() => undefined),
15
27
  listBidsByProduct(id, { pageSize: 20 }).catch(() => null),
16
28
  ]);
17
- const relatedDocs = product
18
- ? await productRepository
19
- .findByCategory(product.category ?? "")
20
- .catch(() => [])
21
- : [];
22
29
  if (!product) {
23
30
  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: "Auction Not Found" }), _jsx(Text, { className: "text-zinc-500", children: "This auction may have ended or the link is incorrect." }), _jsx(Link, { href: String(ROUTES.PUBLIC.AUCTIONS), className: "text-sm font-medium text-primary-600 hover:underline", children: "Browse Auctions" })] }) }) }) }));
24
31
  }
25
32
  const p = product;
26
33
  const currency = p.currency || getDefaultCurrency();
27
- const currentBid = typeof p.currentBid === "number" ? p.currentBid : p.startingBid ?? p.price ?? 0;
28
- const currentBidFormatted = new Intl.NumberFormat(undefined, {
29
- style: "currency",
30
- currency,
31
- }).format(currentBid);
32
- const startingBidFormatted = new Intl.NumberFormat(undefined, {
33
- style: "currency",
34
- currency,
35
- }).format(p.startingBid ?? p.price ?? 0);
36
- const images = Array.isArray(p.images) ? p.images : p.imageUrl ? [p.imageUrl] : [];
37
- const primaryImage = images[0];
34
+ const title = String(p.title ?? p.name ?? "Auction Item");
35
+ const currentBid = typeof p.currentBid === "number"
36
+ ? p.currentBid
37
+ : typeof p.startingBid === "number"
38
+ ? p.startingBid
39
+ : typeof p.price === "number"
40
+ ? p.price
41
+ : 0;
42
+ const startingBid = typeof p.startingBid === "number"
43
+ ? p.startingBid
44
+ : typeof p.price === "number"
45
+ ? p.price
46
+ : 0;
47
+ const minBidIncrement = typeof p.minBidIncrement === "number" ? p.minBidIncrement : 1;
48
+ const images = Array.isArray(p.images)
49
+ ? p.images
50
+ : typeof p.mainImage === "string"
51
+ ? [p.mainImage]
52
+ : [];
38
53
  const endDate = p.auctionEndDate ? new Date(p.auctionEndDate) : null;
39
54
  const isEnded = endDate ? endDate < new Date() : false;
40
55
  const bidCount = typeof p.bidCount === "number" ? p.bidCount : 0;
41
- return (_jsx(AuctionDetailView, { renderGallery: () => primaryImage ? (_jsx(Div, { className: "overflow-hidden rounded-xl border border-zinc-100 dark:border-zinc-800", children: _jsx(Div, { role: "img", "aria-label": p.title ?? p.name ?? "Auction item", className: "aspect-square w-full bg-cover bg-center", style: { backgroundImage: `url(${primaryImage})` } }) })) : (_jsx(Div, { className: "overflow-hidden rounded-xl border border-zinc-100 bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-900", children: _jsx(Div, { className: "flex aspect-square items-center justify-center text-zinc-300 dark:text-zinc-700", children: _jsxs("svg", { width: "64", height: "64", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", "aria-hidden": "true", children: [_jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }), _jsx("circle", { cx: "8.5", cy: "8.5", r: "1.5" }), _jsx("path", { d: "M21 15l-5-5L5 21" })] }) }) })), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 1, className: "text-2xl font-bold text-zinc-900 dark:text-zinc-50", children: p.title ?? p.name ?? "Auction Item" }), _jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Span, { className: "text-2xl font-semibold text-primary-600 dark:text-primary-400", children: currentBidFormatted }), _jsxs(Span, { className: "text-sm text-zinc-500", children: [bidCount, " ", bidCount === 1 ? "bid" : "bids"] }), isEnded && (_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: "Ended" }))] }), endDate && !isEnded && (_jsxs(Text, { className: "text-sm text-zinc-600 dark:text-zinc-400", children: ["Ends: ", endDate.toLocaleString()] })), p.description && (_jsx(Text, { className: "text-sm leading-relaxed text-zinc-600 dark:text-zinc-400", children: typeof p.description === "string" ? p.description : "" })), p.sellerName && (_jsxs(Row, { align: "center", gap: "xs", children: [_jsx(Span, { className: "text-xs text-zinc-500", children: "Listed by" }), _jsx(Span, { className: "text-xs font-medium text-zinc-700 dark:text-zinc-300", children: p.sellerName })] }))] })), renderBidForm: () => (_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: [_jsxs(Text, { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: ["Current bid: ", currentBidFormatted] }), _jsxs(Text, { className: "text-xs text-zinc-500", children: ["Starting bid: ", startingBidFormatted] }), _jsx(Input, { type: "number", placeholder: `Minimum bid`, min: currentBid + 1, "aria-label": "Your bid amount", disabled: isEnded }), _jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: isEnded, children: isEnded ? "Auction Ended" : "Place Bid" })] }) })), renderMobileBidForm: () => !isEnded ? (_jsx(Div, { className: "rounded-xl border border-zinc-100 bg-zinc-50 p-4 dark:border-zinc-800 dark:bg-zinc-900 lg:hidden", children: _jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(Span, { className: "text-base font-semibold text-primary-600", children: currentBidFormatted }), _jsxs(Span, { className: "text-xs text-zinc-500", children: [bidCount, " bids"] })] }), _jsx(Button, { variant: "primary", size: "md", className: "w-full", children: "Place Bid" })] }) })) : null, renderBidHistory: () => {
42
- const bids = (bidsResult?.items ?? []).map((b) => ({
43
- id: b.id,
44
- bidderId: b.userId ?? b.bidderId ?? "",
45
- bidderName: b.bidderName ?? b.userName,
46
- amount: b.bidAmount ?? b.amount ?? 0,
47
- placedAt: b.createdAt ?? b.bidAt ?? "",
48
- }));
49
- return (_jsx(BidHistory, { bids: bids, isEmpty: bids.length === 0, labels: { title: "Bid History" } }));
50
- }, renderRelated: () => {
51
- const related = relatedDocs
52
- .filter((r) => r.id !== product.id && r.isAuction !== false)
53
- .slice(0, 4)
54
- .map((r) => ({
55
- id: String(r.id ?? ""),
56
- title: String(r.name ?? r.title ?? "Auction Item"),
57
- price: typeof r.price === "number" ? r.price : 0,
58
- currency: typeof r.currency === "string" ? r.currency : undefined,
59
- mainImage: Array.isArray(r.images) ? r.images[0] : typeof r.imageUrl === "string" ? r.imageUrl : undefined,
60
- isAuction: true,
61
- auctionEndDate: r.auctionEndDate,
62
- startingBid: typeof r.startingBid === "number" ? r.startingBid : undefined,
63
- currentBid: typeof r.currentBid === "number" ? r.currentBid : undefined,
64
- bidCount: typeof r.bidCount === "number" ? r.bidCount : undefined,
65
- slug: typeof r.slug === "string" ? r.slug : undefined,
66
- }));
67
- if (related.length === 0)
68
- return null;
69
- return (_jsx(RelatedProducts, { labels: { title: "Similar Auctions" }, renderGrid: () => (_jsx(MarketplaceAuctionGrid, { auctions: related, gridClassName: "grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4" })) }));
70
- } }));
56
+ const buyNowPrice = typeof p.buyNowPrice === "number" ? p.buyNowPrice : null;
57
+ const condition = typeof p.condition === "string" ? p.condition : null;
58
+ const featured = p.featured === true;
59
+ const shippingPaidBy = p.shippingPaidBy;
60
+ const freeShipping = shippingPaidBy === "seller";
61
+ const sellerName = typeof p.sellerName === "string" ? p.sellerName : null;
62
+ const safeSeller = sellerName ? safeDisplayName(sellerName, "") : null;
63
+ const tags = Array.isArray(p.tags) ? p.tags : [];
64
+ const features = Array.isArray(p.features) ? p.features : [];
65
+ const descriptionHtml = toDescriptionHtml(p.description);
66
+ const relatedDocs = await productRepository
67
+ .findByCategory(String(p.category ?? ""))
68
+ .catch(() => []);
69
+ return (_jsx(Main, { children: _jsxs(Container, { size: "xl", className: "px-4 py-6", children: [_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.AUCTIONS), className: "hover:text-primary-600 transition-colors", children: "Auctions" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "text-zinc-700 dark:text-zinc-300 truncate max-w-[200px]", children: title })] }), _jsx(AuctionDetailView, { renderGallery: () => (_jsx(ProductGalleryClient, { images: images, productName: title })), renderInfo: () => (_jsxs(Stack, { gap: "sm", children: [_jsxs(Div, { children: [_jsx(Span, { className: "mb-1.5 inline-block rounded-full bg-amber-100 dark:bg-amber-900/30 px-2.5 py-0.5 text-xs font-semibold text-amber-700 dark:text-amber-300", children: "\uD83C\uDFF7\uFE0F Live Auction" }), _jsx(Heading, { level: 1, className: "text-xl font-bold leading-snug text-zinc-900 dark:text-zinc-50 sm:text-2xl", children: title })] }), _jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-xs text-zinc-500", children: "Current bid" }), _jsx(Span, { className: "text-2xl font-bold text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) })] }), _jsxs(Span, { className: "rounded-full bg-zinc-100 dark:bg-zinc-800 px-3 py-1 text-sm font-medium text-zinc-600 dark:text-zinc-300", children: [bidCount, " ", bidCount === 1 ? "bid" : "bids"] }), isEnded ? (_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: "Ended" })) : (_jsx(Span, { className: "rounded-full bg-emerald-100 px-2.5 py-0.5 text-xs font-medium text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400", children: "Active" }))] }), endDate && (_jsxs(Row, { align: "center", gap: "xs", className: "text-sm text-zinc-600 dark:text-zinc-400", children: [_jsxs(Span, { children: [isEnded ? "Ended" : "Ends", ":"] }), _jsx(Span, { className: "font-medium", children: endDate.toLocaleString() })] })), buyNowPrice !== null && !isEnded && (_jsxs(Row, { align: "center", gap: "sm", className: "rounded-lg border border-primary-200 dark:border-primary-800 bg-primary-50 dark:bg-primary-900/20 px-3 py-2", children: [_jsx(Span, { className: "text-xs text-zinc-600 dark:text-zinc-400", children: "Buy Now:" }), _jsx(Span, { className: "text-base font-bold text-primary-700 dark:text-primary-300", children: formatCurrency(buyNowPrice, currency) })] })), _jsx(ProductFeatureBadges, { featured: featured, freeShipping: freeShipping, condition: condition ?? undefined, labels: {
70
+ featured: "Featured",
71
+ fasterDelivery: "Faster Delivery",
72
+ ratedSeller: "Rated Seller",
73
+ condition: "Condition",
74
+ conditionNew: "New",
75
+ conditionUsed: "Used",
76
+ conditionBroken: "For Parts",
77
+ conditionRefurbished: "Refurbished",
78
+ returnable: "Returnable",
79
+ freeShipping: "Free Shipping",
80
+ codAvailable: "Cash on Delivery",
81
+ wishlistCount: (n) => `${n} wishlisted`,
82
+ categoryProductCount: (n, cat) => `${n} in ${cat}`,
83
+ } }), 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 item" }), _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: "Listed by" }), _jsx(Span, { className: "text-xs font-medium text-zinc-700 dark:text-zinc-300", children: safeSeller })] }))] })), renderBidForm: () => (_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: [_jsxs(Div, { className: "space-y-1", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-xs text-zinc-500", children: "Current bid" }), _jsx(Text, { className: "text-xs text-zinc-500", children: "Starting bid" })] }), _jsxs(Row, { justify: "between", align: "baseline", children: [_jsx(Span, { className: "text-xl font-bold text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsx(Span, { className: "text-sm text-zinc-500", children: formatCurrency(startingBid, currency) })] }), _jsxs(Text, { className: "text-xs text-zinc-400 dark:text-zinc-500", children: [bidCount, " ", bidCount === 1 ? "bid" : "bids", " \u00B7 min increment ", formatCurrency(minBidIncrement, currency)] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Input, { type: "number", placeholder: `At least ${formatCurrency(currentBid + minBidIncrement, currency)}`, min: currentBid + minBidIncrement, "aria-label": "Your bid amount", disabled: isEnded }), _jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: isEnded, children: isEnded ? "Auction Ended" : "Place Bid" }), buyNowPrice !== null && !isEnded && (_jsxs(Button, { variant: "secondary", size: "md", className: "w-full", children: ["Buy Now \u2014 ", formatCurrency(buyNowPrice, currency)] }))] }), tags.length > 0 && (_jsx(Div, { className: "border-t border-zinc-200 dark:border-zinc-700 pt-4", children: _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))) }) }))] })), renderMobileBidForm: () => !isEnded ? (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-4 lg:hidden", children: [_jsxs(Row, { align: "center", gap: "sm", className: "mb-3", children: [_jsx(Span, { className: "text-base font-bold text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { className: "text-xs text-zinc-500", children: [bidCount, " bids"] })] }), _jsx(Button, { variant: "primary", size: "md", className: "w-full", children: "Place Bid" })] })) : null, renderBidHistory: () => {
84
+ const bids = (bidsResult?.items ?? []).map((b) => ({
85
+ id: String(b.id ?? ""),
86
+ bidderId: String(b.userId ?? b.bidderId ?? ""),
87
+ bidderName: (b.bidderName ?? b.userName),
88
+ amount: (typeof b.bidAmount === "number" ? b.bidAmount : typeof b.amount === "number" ? b.amount : 0),
89
+ placedAt: (b.createdAt ?? b.bidAt ?? ""),
90
+ }));
91
+ return (_jsx(BidHistory, { bids: bids, isEmpty: bids.length === 0, labels: { title: "Bid History" } }));
92
+ }, renderRelated: () => {
93
+ const related = relatedDocs
94
+ .filter((r) => r.id !== product.id && r.isAuction !== false)
95
+ .slice(0, 4)
96
+ .map((r) => ({
97
+ id: String(r.id ?? ""),
98
+ title: String(r.title ?? r.name ?? "Auction Item"),
99
+ price: typeof r.price === "number" ? r.price : 0,
100
+ currency: typeof r.currency === "string" ? r.currency : undefined,
101
+ mainImage: Array.isArray(r.images)
102
+ ? r.images[0]
103
+ : typeof r.mainImage === "string"
104
+ ? r.mainImage
105
+ : undefined,
106
+ isAuction: true,
107
+ auctionEndDate: r.auctionEndDate,
108
+ startingBid: typeof r.startingBid === "number" ? r.startingBid : undefined,
109
+ currentBid: typeof r.currentBid === "number" ? r.currentBid : undefined,
110
+ bidCount: typeof r.bidCount === "number" ? r.bidCount : undefined,
111
+ slug: typeof r.slug === "string" ? r.slug : undefined,
112
+ }));
113
+ if (related.length === 0)
114
+ return null;
115
+ return (_jsx(RelatedProducts, { labels: { title: "Similar Auctions" }, renderGrid: () => (_jsx(MarketplaceAuctionGrid, { auctions: related, gridClassName: "grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4" })) }));
116
+ } }), !isEnded && (_jsxs(BuyBar, { children: [_jsx(Span, { className: "mr-auto text-sm font-bold text-zinc-900 dark:text-zinc-50", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { className: "text-xs text-zinc-400 dark:text-zinc-500 mr-1", children: [bidCount, " bid", bidCount !== 1 ? "s" : ""] }), _jsx(Button, { variant: "primary", size: "sm", className: "shrink-0", children: "Place Bid" })] }))] }) }));
71
117
  }
@@ -0,0 +1,8 @@
1
+ import type { FacetOption } from "../../filters/FilterFacetSection";
2
+ import type { UrlTable } from "../../filters/FilterPanel";
3
+ export interface AuctionFiltersProps {
4
+ table: UrlTable;
5
+ storeOptions?: FacetOption[];
6
+ currencyPrefix?: string;
7
+ }
8
+ export declare function AuctionFilters({ table, storeOptions, currencyPrefix, }: AuctionFiltersProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useTranslations } from "next-intl";
3
+ import { FilterFacetSection } from "../../filters/FilterFacetSection";
4
+ import { RangeFilter } from "../../filters/RangeFilter";
5
+ import { Div } from "../../../ui";
6
+ export function AuctionFilters({ table, storeOptions = [], currencyPrefix = "", }) {
7
+ const t = useTranslations("filters");
8
+ const selectedStores = table.get("storeId")
9
+ ? table.get("storeId").split("|").filter(Boolean)
10
+ : [];
11
+ return (_jsxs(Div, { children: [_jsx(RangeFilter, { title: t("bidPriceRange"), minValue: table.get("minBid"), maxValue: table.get("maxBid"), onMinChange: (v) => table.set("minBid", v), onMaxChange: (v) => table.set("maxBid", v), prefix: currencyPrefix, showSlider: true, minBound: 0, maxBound: 500000, step: 500, minPlaceholder: t("minBidPrice"), maxPlaceholder: t("maxBidPrice"), defaultCollapsed: false }), storeOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("store"), options: storeOptions, selected: selectedStores, onChange: (vals) => table.set("storeId", vals[0] ?? ""), searchable: storeOptions.length > 6, defaultCollapsed: true })), _jsx(RangeFilter, { title: t("dateRange"), type: "date", minValue: table.get("dateFrom"), maxValue: table.get("dateTo"), onMinChange: (v) => table.set("dateFrom", v), onMaxChange: (v) => table.set("dateTo", v), minPlaceholder: t("minDate"), maxPlaceholder: t("maxDate"), defaultCollapsed: true })] }));
12
+ }
@@ -1 +1,6 @@
1
- export declare function AuctionsListView(): Promise<import("react/jsx-runtime").JSX.Element>;
1
+ type SearchParams = Record<string, string | string[]>;
2
+ export interface AuctionsListViewProps {
3
+ searchParams?: SearchParams;
4
+ }
5
+ export declare function AuctionsListView({ searchParams }: AuctionsListViewProps): Promise<import("react/jsx-runtime").JSX.Element>;
6
+ export {};
@@ -3,13 +3,45 @@ import { productRepository } from "../../../repositories";
3
3
  import { Container, Heading, Main, Section } from "../../../ui";
4
4
  import { AdSlot } from "../../homepage/components/AdSlot";
5
5
  import { AuctionsIndexListing } from "../../products/components/AuctionsIndexListing";
6
- export async function AuctionsListView() {
6
+ function sp(params, key) {
7
+ const v = params[key];
8
+ return Array.isArray(v) ? v[0] ?? "" : v ?? "";
9
+ }
10
+ function buildAuctionFilters(params) {
11
+ const parts = ["status==published", "isAuction==true"];
12
+ const minBid = sp(params, "minBid");
13
+ const maxBid = sp(params, "maxBid");
14
+ if (minBid)
15
+ parts.push(`currentBid>=${minBid}`);
16
+ if (maxBid)
17
+ parts.push(`currentBid<=${maxBid}`);
18
+ const store = sp(params, "store");
19
+ if (store) {
20
+ const values = store.split("|").filter(Boolean);
21
+ if (values.length === 1)
22
+ parts.push(`sellerId==${values[0]}`);
23
+ else if (values.length > 1)
24
+ parts.push(`sellerId==${values.join("|")}`);
25
+ }
26
+ const dateFrom = sp(params, "dateFrom");
27
+ const dateTo = sp(params, "dateTo");
28
+ if (dateFrom)
29
+ parts.push(`auctionEndDate>=${dateFrom}`);
30
+ if (dateTo)
31
+ parts.push(`auctionEndDate<=${dateTo}`);
32
+ return parts.join(",");
33
+ }
34
+ export async function AuctionsListView({ searchParams = {} }) {
35
+ const sort = sp(searchParams, "sort") || "auctionEndDate";
36
+ const page = Number(sp(searchParams, "page")) || 1;
37
+ const pageSize = Number(sp(searchParams, "pageSize")) || 24;
38
+ const filters = buildAuctionFilters(searchParams);
7
39
  const result = await productRepository
8
40
  .list({
9
- filters: "status==published,isAuction==true",
10
- sorts: "auctionEndDate",
11
- page: 1,
12
- pageSize: 24,
41
+ filters,
42
+ sorts: sort,
43
+ page,
44
+ pageSize,
13
45
  })
14
46
  .catch(() => null);
15
47
  const initial = result ?? null;
@@ -64,8 +64,8 @@ export declare const auctionItemSchema: z.ZodObject<{
64
64
  price: number;
65
65
  isAuction: true;
66
66
  startingBid: number;
67
- bidCount: number;
68
67
  auctionEndDate: string;
68
+ bidCount: number;
69
69
  media?: {
70
70
  url: string;
71
71
  type: "video" | "image" | "file";
@@ -76,8 +76,8 @@ export declare const auctionItemSchema: z.ZodObject<{
76
76
  description?: string | undefined;
77
77
  storeSlug?: string | undefined;
78
78
  storeId?: string | undefined;
79
- mainImage?: string | undefined;
80
79
  currentBid?: number | undefined;
80
+ mainImage?: string | undefined;
81
81
  }, {
82
82
  id: string;
83
83
  title: string;
@@ -91,8 +91,8 @@ export declare const auctionItemSchema: z.ZodObject<{
91
91
  price: number;
92
92
  isAuction: true;
93
93
  startingBid: number;
94
- bidCount: number;
95
94
  auctionEndDate: string;
95
+ bidCount: number;
96
96
  media?: {
97
97
  url: string;
98
98
  type: "video" | "image" | "file";
@@ -103,8 +103,8 @@ export declare const auctionItemSchema: z.ZodObject<{
103
103
  description?: string | undefined;
104
104
  storeSlug?: string | undefined;
105
105
  storeId?: string | undefined;
106
- mainImage?: string | undefined;
107
106
  currentBid?: number | undefined;
107
+ mainImage?: string | undefined;
108
108
  }>;
109
109
  export declare const bidRecordSchema: z.ZodObject<{
110
110
  id: z.ZodString;
@@ -1,17 +1,11 @@
1
- import React from "react";
2
1
  import type { BlogPost } from "../types";
3
2
  export interface BlogFeaturedCardProps {
4
3
  post: BlogPost;
5
- /** Full URL/path to the post */
6
4
  href: string;
7
5
  labels?: {
8
6
  featuredBadge?: string;
9
7
  readTime?: string;
10
8
  };
11
- /** Render-prop for wrapping in a link */
12
- renderLink?: (href: string, children: React.ReactNode) => React.ReactNode;
13
- /** Render-prop for the cover image slot */
14
- renderImage?: (post: BlogPost) => React.ReactNode;
15
9
  className?: string;
16
10
  }
17
- export declare function BlogFeaturedCard({ post, href, labels, renderLink, renderImage, className, }: BlogFeaturedCardProps): import("react/jsx-runtime").JSX.Element;
11
+ export declare function BlogFeaturedCard({ post, href, labels, className, }: BlogFeaturedCardProps): import("react/jsx-runtime").JSX.Element;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Article, Div, Heading, Row, Span, Text } from "../../../ui";
3
- import { THEME_CONSTANTS, LAYOUT } from "../../../tokens";
2
+ import { Article, Div, Heading, Row, Span, Text, TextLink } from "../../../ui";
3
+ import { THEME_CONSTANTS } from "../../../tokens";
4
4
  import { getMediaUrl } from "../../media/types/index";
5
5
  import { getDefaultLocale } from "../../../core/baseline-resolver";
6
6
  import { safeDisplayName } from "../../../security";
@@ -11,7 +11,7 @@ const CATEGORY_BADGE = {
11
11
  updates: "bg-purple-100 text-purple-800 dark:bg-purple-900/40 dark:text-purple-300",
12
12
  community: "bg-orange-100 text-orange-800 dark:bg-orange-900/40 dark:text-orange-300",
13
13
  };
14
- export function BlogFeaturedCard({ post, href, labels = {}, renderLink, renderImage, className = "", }) {
14
+ export function BlogFeaturedCard({ post, href, labels = {}, className = "", }) {
15
15
  const safeTitle = post.title?.trim() || "Untitled post";
16
16
  const coverImageUrl = getMediaUrl(post.coverImage);
17
17
  const date = post.publishedAt
@@ -21,6 +21,5 @@ export function BlogFeaturedCard({ post, href, labels = {}, renderLink, renderIm
21
21
  day: "numeric",
22
22
  })
23
23
  : "";
24
- const inner = (_jsxs(Article, { className: `overflow-hidden rounded-xl border border-neutral-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm hover:shadow-lg transition-shadow duration-200 md:flex ${className}`, children: [_jsx(Div, { className: `md:w-1/2 relative ${LAYOUT.cardHeight.blog} overflow-hidden`, children: renderImage ? (renderImage(post)) : (coverImageUrl ? (_jsx(Div, { role: "img", "aria-label": safeTitle, className: "h-full w-full bg-cover bg-center", style: { backgroundImage: `url(${coverImageUrl})` } })) : (_jsx(Div, { role: "img", "aria-label": safeTitle, className: "h-full w-full bg-gradient-to-br from-zinc-200 via-zinc-100 to-zinc-300 dark:from-slate-800 dark:via-slate-700 dark:to-slate-900" }))) }), _jsxs(Div, { className: "p-8 md:w-1/2 flex flex-col justify-center", children: [_jsxs(Row, { className: "gap-2 mb-3", children: [_jsx(Span, { className: `inline-block px-2 py-0.5 rounded-full text-xs font-medium capitalize ${CATEGORY_BADGE[post.category] ?? ""}`, children: post.category }), post.isFeatured && (_jsx(Span, { className: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/40 dark:text-yellow-300 px-2 py-0.5 rounded-full text-xs font-medium", children: labels.featuredBadge ?? "Featured" }))] }), _jsx(Heading, { level: 2, className: "text-xl font-semibold text-neutral-900 dark:text-zinc-100 mb-3 leading-snug", children: safeTitle }), post.excerpt && (_jsx(Text, { className: `text-neutral-500 dark:text-zinc-400 mb-4 ${THEME_CONSTANTS.utilities.textClamp3} text-sm`, children: post.excerpt })), _jsxs(Row, { className: "gap-4 text-xs text-neutral-400 dark:text-zinc-500", children: [post.authorName && _jsx(Span, { children: safeDisplayName(post.authorName, "Author") }), post.readTimeMinutes != null && (_jsxs(Span, { children: [post.readTimeMinutes, " ", labels.readTime ?? "min read"] })), date && _jsx(Span, { children: date })] })] })] }));
25
- return (_jsx(Div, { className: "mb-10", children: renderLink ? renderLink(href, inner) : inner }));
24
+ return (_jsx(TextLink, { href: href, className: "block h-full", children: _jsxs(Article, { className: `flex h-full flex-col overflow-hidden rounded-xl border border-neutral-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm hover:shadow-md transition-shadow duration-200 ${className}`, children: [_jsx(Div, { className: "aspect-video overflow-hidden flex-shrink-0", children: coverImageUrl ? (_jsx(Div, { role: "img", "aria-label": safeTitle, className: "h-full w-full bg-cover bg-center transition-transform duration-300 group-hover:scale-105", style: { backgroundImage: `url(${coverImageUrl})` } })) : (_jsx(Div, { role: "img", "aria-label": safeTitle, className: "h-full w-full bg-gradient-to-br from-zinc-200 via-zinc-100 to-zinc-300 dark:from-slate-800 dark:via-slate-700 dark:to-slate-900" })) }), _jsxs(Div, { className: "flex flex-1 flex-col p-4", children: [_jsxs(Row, { className: "gap-1.5 mb-2 flex-wrap", children: [_jsx(Span, { className: `inline-block px-2 py-0.5 rounded-full text-xs font-medium capitalize ${CATEGORY_BADGE[post.category] ?? "bg-zinc-100 text-zinc-700"}`, children: post.category }), post.isFeatured && (_jsx(Span, { className: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/40 dark:text-yellow-300 px-2 py-0.5 rounded-full text-xs font-medium", children: labels.featuredBadge ?? "Featured" }))] }), _jsx(Heading, { level: 3, className: `font-semibold text-neutral-900 dark:text-zinc-100 text-base leading-snug mb-1 ${THEME_CONSTANTS.utilities.textClamp2}`, children: safeTitle }), post.excerpt && (_jsx(Text, { className: `text-neutral-500 dark:text-zinc-400 text-sm ${THEME_CONSTANTS.utilities.textClamp2} mb-3`, children: post.excerpt })), _jsxs(Row, { className: "mt-auto gap-3 text-xs text-neutral-400 dark:text-zinc-500 flex-wrap", children: [post.authorName && (_jsx(Span, { children: safeDisplayName(post.authorName, "Author") })), post.readTimeMinutes != null && (_jsxs(Span, { children: [post.readTimeMinutes, " ", labels.readTime ?? "min read"] })), date && _jsx(Span, { children: date })] })] })] }) }));
26
25
  }
@@ -1,6 +1,7 @@
1
1
  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
+ import { RangeFilter } from "../../filters/RangeFilter";
4
5
  import { SwitchFilter } from "../../filters/SwitchFilter";
5
6
  import { Div } from "../../../ui";
6
7
  export const BLOG_FILTER_KEYS = {
@@ -62,5 +63,5 @@ export function BlogFilters({ table, variant = "admin" }) {
62
63
  const selectedCategory = table.get("category")
63
64
  ? table.get("category").split("|").filter(Boolean)
64
65
  : [];
65
- return (_jsxs(Div, { children: [variant !== "public" && (_jsx(FilterFacetSection, { title: t("status"), options: statusOptions, selected: selectedStatus, onChange: (vals) => table.set("status", vals.join("|")), searchable: false, defaultCollapsed: false })), _jsx(FilterFacetSection, { title: t("category"), options: categoryOptions, selected: selectedCategory, onChange: (vals) => table.set("category", vals.join("|")), searchable: false, defaultCollapsed: variant !== "public" }), variant !== "public" && (_jsx(SwitchFilter, { title: t("isFeatured"), label: t("showFeaturedOnly"), checked: table.get("isFeatured") === "true", onChange: (v) => table.set("isFeatured", v ? "true" : ""), defaultCollapsed: true }))] }));
66
+ return (_jsxs(Div, { children: [variant !== "public" && (_jsx(FilterFacetSection, { title: t("status"), options: statusOptions, selected: selectedStatus, onChange: (vals) => table.set("status", vals.join("|")), searchable: false, defaultCollapsed: false })), _jsx(FilterFacetSection, { title: t("category"), options: categoryOptions, selected: selectedCategory, onChange: (vals) => table.set("category", vals.join("|")), searchable: false, defaultCollapsed: variant !== "public" }), variant !== "public" && (_jsx(SwitchFilter, { title: t("isFeatured"), label: t("showFeaturedOnly"), checked: table.get("isFeatured") === "true", onChange: (v) => table.set("isFeatured", v ? "true" : ""), defaultCollapsed: true })), _jsx(RangeFilter, { title: t("dateRange"), type: "date", minValue: table.get("dateFrom"), maxValue: table.get("dateTo"), onMinChange: (v) => table.set("dateFrom", v), onMaxChange: (v) => table.set("dateTo", v), minPlaceholder: t("minDate"), maxPlaceholder: t("maxDate"), defaultCollapsed: true }), _jsx(RangeFilter, { title: t("votesRange"), minValue: table.get("minVotes"), maxValue: table.get("maxVotes"), onMinChange: (v) => table.set("minVotes", v), onMaxChange: (v) => table.set("maxVotes", v), minBound: 0, maxBound: 100000, step: 10, minPlaceholder: t("minVotes"), maxPlaceholder: t("maxVotes"), defaultCollapsed: true })] }));
66
67
  }
@@ -1,14 +1,18 @@
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, X } from "lucide-react";
3
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
4
6
  import { useBlogPosts } from "../hooks/useBlog";
5
- import { Div, Grid, Input, Pagination, SlottedListingView, SortDropdown, Stack, Text, } from "../../../ui";
7
+ import { Pagination, SortDropdown } from "../../../ui";
6
8
  import { ROUTES } from "../../../next";
7
9
  import { BlogCard } from "./BlogListView";
8
- import { BlogFilters, BLOG_PUBLIC_SORT_OPTIONS, } from "./BlogFilters";
10
+ import { BlogFilters, BLOG_PUBLIC_SORT_OPTIONS } from "./BlogFilters";
9
11
  const PAGE_SIZE = 24;
10
12
  export function BlogIndexListing({ initialData }) {
11
13
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: "-publishedAt" } });
14
+ const [searchInput, setSearchInput] = useState(table.get("q") || "");
15
+ const [filterOpen, setFilterOpen] = useState(false);
12
16
  const params = {
13
17
  q: table.get("q") || undefined,
14
18
  category: (table.get("category") || undefined),
@@ -16,11 +20,12 @@ export function BlogIndexListing({ initialData }) {
16
20
  page: table.getNumber("page", 1),
17
21
  perPage: table.getNumber("pageSize", PAGE_SIZE),
18
22
  };
19
- const { posts, total, totalPages, isLoading } = useBlogPosts(params, {
20
- initialData,
21
- });
23
+ const { posts, total, totalPages, isLoading } = useBlogPosts(params, { initialData });
22
24
  const currentPage = table.getNumber("page", 1);
23
- 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 posts...", className: "max-w-sm" })), renderSort: (value, onChange) => (_jsx(SortDropdown, { value: value, onChange: onChange, options: BLOG_PUBLIC_SORT_OPTIONS })), renderFilters: () => (_jsx(BlogFilters, { table: table, variant: "public" })), renderTable: () => posts.length === 0 && !isLoading ? (_jsx(Stack, { align: "center", gap: "3", className: "justify-center py-24 text-center", children: _jsx(Text, { className: "text-xl font-medium text-zinc-900 dark:text-zinc-50", children: "No posts found" }) })) : (_jsx(Grid, { cols: 3, gap: "md", children: posts.map((post) => (_jsx(BlogCard, { post: post, onClick: () => {
24
- window.location.href = String(ROUTES.BLOG.ARTICLE(post.slug));
25
- } }, post.id))) })), renderPagination: () => totalPages > 1 ? (_jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) })) : null, total: total, isLoading: isLoading }) }));
25
+ const commitSearch = useCallback(() => {
26
+ table.set("q", searchInput.trim());
27
+ table.setPage(1);
28
+ }, [searchInput, table]);
29
+ const closeFilters = () => setFilterOpen(false);
30
+ 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: (e) => e.key === "Enter" && commitSearch(), placeholder: "Search posts...", 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") || "-publishedAt", onChange: (v) => { table.set("sort", v); table.setPage(1); }, options: BLOG_PUBLIC_SORT_OPTIONS })] })] }) }), _jsxs("div", { className: "py-6", children: [isLoading ? (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: Array.from({ length: 6 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-video bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-5 space-y-2", children: [_jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/4" }), _jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-full" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-2/3" })] })] }, i))) })) : posts.length === 0 ? (_jsx("p", { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "No posts found." })) : (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: posts.map((post) => (_jsx(BlogCard, { post: post, href: String(ROUTES.BLOG.ARTICLE(post.slug)) }, post.id))) })), totalPages > 1 && (_jsx("div", { className: "mt-8 flex justify-center", children: _jsx(Pagination, { currentPage: currentPage, 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(BlogFilters, { table: table, variant: "public" }) }), _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" }) })] })] }))] }));
26
31
  }
@@ -1 +1,6 @@
1
- export declare function BlogIndexPageView(): Promise<import("react/jsx-runtime").JSX.Element>;
1
+ type SearchParams = Record<string, string | string[]>;
2
+ export interface BlogIndexPageViewProps {
3
+ searchParams?: SearchParams;
4
+ }
5
+ export declare function BlogIndexPageView({ searchParams }: BlogIndexPageViewProps): Promise<import("react/jsx-runtime").JSX.Element>;
6
+ export {};
@@ -3,9 +3,17 @@ import { blogRepository } from "../../../repositories";
3
3
  import { Container, Heading, Main, Section } from "../../../ui";
4
4
  import { AdSlot } from "../../homepage/components/AdSlot";
5
5
  import { BlogIndexListing } from "./BlogIndexListing";
6
- export async function BlogIndexPageView() {
6
+ function sp(params, key) {
7
+ const v = params[key];
8
+ return Array.isArray(v) ? v[0] ?? "" : v ?? "";
9
+ }
10
+ export async function BlogIndexPageView({ searchParams = {} }) {
11
+ const sort = sp(searchParams, "sort") || "-publishedAt";
12
+ const page = Number(sp(searchParams, "page")) || 1;
13
+ const pageSize = Number(sp(searchParams, "pageSize")) || 24;
14
+ const category = sp(searchParams, "category");
7
15
  const result = await blogRepository
8
- .listPublished({}, { sorts: "-publishedAt", page: 1, pageSize: 24 })
16
+ .listPublished({ ...(category ? { category } : {}) }, { sorts: sort, page, pageSize })
9
17
  .catch(() => null);
10
18
  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 dark:text-zinc-50", children: "Blog" }), _jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-6" }), _jsx(BlogIndexListing, { initialData: result }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] }) }) }));
11
19
  }
@@ -2,10 +2,11 @@ import type { LayoutSlots } from "../../../contracts";
2
2
  import type { BlogPost, BlogPostCategory } from "../types";
3
3
  interface BlogCardProps {
4
4
  post: BlogPost;
5
+ href?: string;
5
6
  onClick?: (post: BlogPost) => void;
6
7
  className?: string;
7
8
  }
8
- export declare function BlogCard({ post, onClick, className }: BlogCardProps): import("react/jsx-runtime").JSX.Element;
9
+ export declare function BlogCard({ post, href, onClick, className }: BlogCardProps): import("react/jsx-runtime").JSX.Element;
9
10
  interface BlogCategoryTabsProps {
10
11
  categories: BlogPostCategory[];
11
12
  active?: BlogPostCategory | null;
@@ -1,10 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React from "react";
3
+ import Link from "next/link";
3
4
  import { Article, Button, Div, Heading, Pagination, Row, Span, Text, } from "../../../ui";
4
5
  import { THEME_CONSTANTS } from "../../../tokens";
5
6
  import { getDefaultLocale } from "../../../core/baseline-resolver";
6
7
  import { getMediaUrl } from "../../media/types/index";
7
- export function BlogCard({ post, onClick, className = "" }) {
8
+ import { safeDisplayName } from "../../../security";
9
+ export function BlogCard({ post, href, onClick, className = "" }) {
8
10
  const coverImageUrl = getMediaUrl(post.coverImage);
9
11
  const date = post.publishedAt
10
12
  ? new Date(post.publishedAt).toLocaleDateString(getDefaultLocale(), {
@@ -13,9 +15,14 @@ export function BlogCard({ post, onClick, className = "" }) {
13
15
  day: "numeric",
14
16
  })
15
17
  : "";
16
- return (_jsxs(Article, { role: onClick ? "button" : undefined, tabIndex: onClick ? 0 : undefined, onKeyDown: onClick
18
+ const isInteractive = !!(href || onClick);
19
+ const card = (_jsxs(Article, { role: onClick && !href ? "button" : undefined, tabIndex: onClick && !href ? 0 : undefined, onKeyDown: onClick && !href
17
20
  ? (e) => (e.key === "Enter" || e.key === " ") && onClick(post)
18
- : undefined, onClick: onClick ? () => onClick(post) : undefined, className: `group flex flex-col h-full overflow-hidden rounded-xl border border-neutral-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm transition hover:shadow-md ${onClick ? "cursor-pointer" : ""} ${className}`, children: [coverImageUrl && (_jsx(Div, { className: "aspect-video w-full overflow-hidden bg-neutral-100 dark:bg-slate-800", children: _jsx(Div, { role: "img", "aria-label": post.title, className: "h-full w-full bg-center bg-cover transition-transform duration-300 group-hover:scale-105", style: { backgroundImage: `url(${coverImageUrl})` } }) })), _jsxs(Div, { className: "flex flex-1 flex-col p-5", children: [_jsxs(Row, { className: "mb-2 gap-2", children: [_jsx(Span, { className: "rounded-full bg-primary/10 px-2 py-0.5 text-xs font-medium capitalize text-primary", children: post.category }), post.readTimeMinutes && (_jsxs(Span, { className: "text-xs text-neutral-400 dark:text-zinc-500", children: [post.readTimeMinutes, " min read"] }))] }), _jsx(Heading, { level: 3, className: `${THEME_CONSTANTS.utilities.textClamp2} text-base font-semibold text-neutral-900 dark:text-zinc-100 group-hover:text-primary`, children: post.title }), post.excerpt && (_jsx(Text, { className: `mt-2 ${THEME_CONSTANTS.utilities.textClamp3} flex-1 text-sm text-neutral-500 dark:text-zinc-400`, children: post.excerpt })), _jsxs(Row, { className: "mt-4 gap-3", children: [post.authorAvatar && (_jsx(Div, { role: "img", "aria-label": post.authorName ?? "author", className: "h-7 w-7 rounded-full bg-center bg-cover", style: { backgroundImage: `url(${post.authorAvatar})` } })), _jsxs(Text, { className: "text-xs text-neutral-500 dark:text-zinc-400", children: [post.authorName && (_jsx(Span, { className: "font-medium text-neutral-700 dark:text-zinc-300", children: post.authorName })), date && _jsxs(Span, { className: "ml-1", children: ["\u00B7 ", date] })] })] })] })] }));
21
+ : undefined, onClick: onClick && !href ? () => onClick(post) : undefined, className: `group flex flex-col h-full overflow-hidden rounded-xl border border-neutral-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm transition hover:shadow-md ${isInteractive ? "cursor-pointer" : ""} ${className}`, children: [_jsx(Div, { className: "aspect-video w-full overflow-hidden bg-neutral-100 dark:bg-slate-800 flex-shrink-0", children: coverImageUrl ? (_jsx(Div, { role: "img", "aria-label": post.title, className: "h-full w-full bg-center bg-cover transition-transform duration-300 group-hover:scale-105", style: { backgroundImage: `url(${coverImageUrl})` } })) : (_jsx(Div, { className: "h-full w-full bg-gradient-to-br from-zinc-200 via-zinc-100 to-zinc-300 dark:from-slate-800 dark:via-slate-700 dark:to-slate-900 flex items-center justify-center", children: _jsx("span", { className: "text-4xl opacity-30", "aria-hidden": "true", children: "\u270D\uFE0F" }) })) }), _jsxs(Div, { className: "flex flex-1 flex-col p-5", children: [_jsxs(Row, { className: "mb-2 gap-2 flex-wrap", children: [_jsx(Span, { className: "rounded-full bg-primary/10 px-2 py-0.5 text-xs font-medium capitalize text-primary", children: post.category }), post.isFeatured && (_jsx(Span, { className: "rounded-full bg-yellow-100 px-2 py-0.5 text-xs font-medium text-yellow-700 dark:bg-yellow-900/40 dark:text-yellow-300", children: "Featured" })), post.readTimeMinutes && (_jsxs(Span, { className: "text-xs text-neutral-400 dark:text-zinc-500", children: [post.readTimeMinutes, " min read"] }))] }), _jsx(Heading, { level: 3, className: `${THEME_CONSTANTS.utilities.textClamp2} text-base font-semibold text-neutral-900 dark:text-zinc-100 group-hover:text-primary`, children: post.title }), post.excerpt && (_jsx(Text, { className: `mt-2 ${THEME_CONSTANTS.utilities.textClamp3} flex-1 text-sm text-neutral-500 dark:text-zinc-400`, children: post.excerpt })), _jsxs(Row, { className: "mt-auto pt-3 gap-3 items-center", children: [post.authorAvatar ? (_jsx(Div, { role: "img", "aria-label": post.authorName ?? "author", className: "h-7 w-7 flex-shrink-0 rounded-full bg-center bg-cover", style: { backgroundImage: `url(${post.authorAvatar})` } })) : post.authorName ? (_jsx(Div, { className: "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full bg-primary/10 text-xs font-bold text-primary", children: post.authorName.charAt(0).toUpperCase() })) : null, _jsxs(Text, { className: "text-xs text-neutral-500 dark:text-zinc-400 min-w-0", children: [post.authorName && (_jsx(Span, { className: "font-medium text-neutral-700 dark:text-zinc-300", children: safeDisplayName(post.authorName, "Author") })), date && _jsxs(Span, { className: "ml-1 text-neutral-400", children: ["\u00B7 ", date] })] })] })] })] }));
22
+ if (href) {
23
+ return (_jsx(Link, { href: href, className: "block h-full", children: card }));
24
+ }
25
+ return card;
19
26
  }
20
27
  export function BlogCategoryTabs({ categories, active, onSelect, labels = {}, }) {
21
28
  return (_jsxs(Div, { className: "scrollbar-none flex gap-2 overflow-x-auto pb-1", children: [_jsx(Button, { onClick: () => onSelect(null), variant: !active ? "primary" : "ghost", size: "sm", className: `whitespace-nowrap rounded-full px-4 py-1.5 text-sm font-medium transition ${!active ? "bg-neutral-900 text-white" : "bg-neutral-100 dark:bg-slate-800 text-neutral-600 dark:text-zinc-300 hover:bg-neutral-200 dark:hover:bg-slate-700"}`, children: labels.all ?? "All" }), categories.map((cat) => (_jsx(Button, { onClick: () => onSelect(cat), variant: active === cat ? "primary" : "ghost", size: "sm", className: `whitespace-nowrap rounded-full px-4 py-1.5 text-sm font-medium capitalize transition ${active === cat ? "bg-neutral-900 text-white" : "bg-neutral-100 dark:bg-slate-800 text-neutral-600 dark:text-zinc-300 hover:bg-neutral-200 dark:hover:bg-slate-700"}`, children: labels[cat] ?? cat }, cat)))] }));
@@ -2,4 +2,4 @@ import type { CategoryItem } from "../types";
2
2
  export interface CategoriesIndexListingProps {
3
3
  initialData?: CategoryItem[];
4
4
  }
5
- export declare function CategoriesIndexListing({ initialData }: CategoriesIndexListingProps): import("react/jsx-runtime").JSX.Element;
5
+ export declare function CategoriesIndexListing({ initialData: _ }: CategoriesIndexListingProps): import("react/jsx-runtime").JSX.Element;