@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.
- package/dist/client.d.ts +12 -8
- package/dist/client.js +7 -4
- package/dist/constants/api-endpoints.d.ts +4 -0
- package/dist/constants/api-endpoints.js +2 -0
- package/dist/core/contact-submissions.repository.d.ts +32 -0
- package/dist/core/contact-submissions.repository.js +49 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +1 -0
- package/dist/features/about/components/HowPayoutsWorkView.js +1 -1
- package/dist/features/account/components/AddressFilters.d.ts +5 -0
- package/dist/features/account/components/AddressFilters.js +20 -0
- package/dist/features/account/components/AddressesIndexListing.d.ts +6 -0
- package/dist/features/account/components/AddressesIndexListing.js +51 -0
- package/dist/features/account/components/UserSidebar.d.ts +7 -3
- package/dist/features/account/components/UserSidebar.js +55 -7
- package/dist/features/account/hooks/useAddresses.d.ts +7 -0
- package/dist/features/account/hooks/useAddresses.js +12 -1
- package/dist/features/admin/actions/admin-read-actions.d.ts +12 -0
- package/dist/features/admin/actions/admin-read-actions.js +18 -0
- package/dist/features/admin/components/AdminBlogView.js +26 -2
- package/dist/features/admin/components/AdminCarouselView.js +18 -2
- package/dist/features/admin/components/AdminCategoriesView.js +27 -2
- package/dist/features/admin/components/AdminContactView.d.ts +4 -0
- package/dist/features/admin/components/AdminContactView.js +50 -0
- package/dist/features/admin/components/AdminFaqsView.js +19 -2
- package/dist/features/admin/components/AdminListingScaffold.d.ts +11 -2
- package/dist/features/admin/components/AdminListingScaffold.js +14 -3
- package/dist/features/admin/components/AdminNewsletterView.d.ts +4 -0
- package/dist/features/admin/components/AdminNewsletterView.js +50 -0
- package/dist/features/admin/components/AdminProductsView.js +30 -2
- package/dist/features/admin/components/AdminReviewsView.js +26 -2
- package/dist/features/admin/components/AdminStoresView.js +17 -2
- package/dist/features/admin/components/AdminUsersView.js +27 -2
- package/dist/features/admin/components/DataTable.d.ts +2 -1
- package/dist/features/admin/components/DataTable.js +18 -4
- package/dist/features/admin/components/index.d.ts +6 -0
- package/dist/features/admin/components/index.js +3 -0
- package/dist/features/admin/hooks/useAdminListingData.d.ts +3 -1
- package/dist/features/admin/hooks/useAdminListingData.js +12 -7
- package/dist/features/admin/server.d.ts +3 -0
- package/dist/features/admin/server.js +2 -0
- package/dist/features/auctions/components/AuctionDetailPageView.js +93 -47
- package/dist/features/auctions/components/AuctionFilters.d.ts +8 -0
- package/dist/features/auctions/components/AuctionFilters.js +12 -0
- package/dist/features/auctions/components/AuctionsListView.d.ts +6 -1
- package/dist/features/auctions/components/AuctionsListView.js +37 -5
- package/dist/features/auctions/schemas/index.d.ts +4 -4
- package/dist/features/blog/components/BlogFeaturedCard.d.ts +1 -7
- package/dist/features/blog/components/BlogFeaturedCard.js +4 -5
- package/dist/features/blog/components/BlogFilters.js +2 -1
- package/dist/features/blog/components/BlogIndexListing.js +14 -9
- package/dist/features/blog/components/BlogIndexPageView.d.ts +6 -1
- package/dist/features/blog/components/BlogIndexPageView.js +10 -2
- package/dist/features/blog/components/BlogListView.d.ts +2 -1
- package/dist/features/blog/components/BlogListView.js +10 -3
- package/dist/features/categories/components/CategoriesIndexListing.d.ts +1 -1
- package/dist/features/categories/components/CategoriesIndexListing.js +41 -38
- package/dist/features/categories/components/CategoriesIndexPageView.d.ts +6 -1
- package/dist/features/categories/components/CategoriesIndexPageView.js +41 -2
- package/dist/features/categories/components/CategoryDetailPageView.js +13 -6
- package/dist/features/categories/components/CategoryDetailTabs.d.ts +5 -0
- package/dist/features/categories/components/CategoryDetailTabs.js +17 -0
- package/dist/features/categories/components/CategoryFilters.js +2 -1
- package/dist/features/categories/components/CategoryGrid.d.ts +2 -1
- package/dist/features/categories/components/CategoryGrid.js +8 -6
- package/dist/features/categories/components/CategoryProductsListing.js +22 -11
- package/dist/features/categories/components/index.d.ts +2 -0
- package/dist/features/categories/components/index.js +1 -0
- package/dist/features/categories/hooks/useCategories.d.ts +20 -0
- package/dist/features/categories/hooks/useCategories.js +50 -1
- package/dist/features/categories/hooks/useCategoryTree.d.ts +17 -0
- package/dist/features/categories/hooks/useCategoryTree.js +65 -0
- package/dist/features/events/components/AdminEventEditorView.d.ts +6 -0
- package/dist/features/events/components/AdminEventEditorView.js +203 -0
- package/dist/features/events/components/AdminEventsView.js +28 -2
- package/dist/features/events/components/EventCard.js +4 -2
- package/dist/features/events/components/EventFilters.js +2 -1
- package/dist/features/events/components/EventsIndexListing.js +40 -10
- package/dist/features/events/components/EventsListPageView.d.ts +6 -1
- package/dist/features/events/components/EventsListPageView.js +40 -7
- package/dist/features/events/components/index.d.ts +2 -0
- package/dist/features/events/components/index.js +1 -0
- package/dist/features/events/hooks/useEvents.js +2 -0
- package/dist/features/events/types/index.d.ts +1 -0
- package/dist/features/homepage/components/BlogArticlesSection.js +1 -1
- package/dist/features/homepage/components/EventsSection.js +1 -1
- package/dist/features/homepage/components/FeaturedAuctionsSection.js +1 -1
- package/dist/features/homepage/components/FeaturedPreOrdersSection.js +1 -1
- package/dist/features/homepage/components/FeaturedProductsSection.js +1 -1
- package/dist/features/homepage/components/FeaturedStoresSection.js +1 -1
- package/dist/features/homepage/components/HeroCarousel.js +1 -1
- package/dist/features/homepage/components/MarketplaceHomepageView.js +27 -17
- package/dist/features/homepage/components/SectionCarousel.js +4 -4
- package/dist/features/homepage/components/ShopByCategorySection.js +2 -2
- package/dist/features/homepage/schemas/firestore.d.ts +1 -1
- package/dist/features/homepage/schemas/firestore.js +2 -1
- package/dist/features/layout/AppLayoutShell.d.ts +6 -2
- package/dist/features/layout/AppLayoutShell.js +7 -3
- package/dist/features/pre-orders/components/MarketplacePreorderCard.d.ts +3 -1
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +6 -2
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +80 -12
- package/dist/features/pre-orders/components/PreOrderFilters.d.ts +8 -0
- package/dist/features/pre-orders/components/PreOrderFilters.js +21 -0
- package/dist/features/pre-orders/components/PreOrdersIndexListing.d.ts +2 -1
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +69 -10
- package/dist/features/pre-orders/components/PreOrdersListView.d.ts +6 -1
- package/dist/features/pre-orders/components/PreOrdersListView.js +26 -7
- package/dist/features/pre-orders/components/index.d.ts +2 -0
- package/dist/features/pre-orders/components/index.js +1 -0
- package/dist/features/products/components/AuctionsIndexListing.d.ts +2 -1
- package/dist/features/products/components/AuctionsIndexListing.js +61 -9
- package/dist/features/products/components/InteractiveProductCard.d.ts +2 -4
- package/dist/features/products/components/InteractiveProductCard.js +2 -2
- package/dist/features/products/components/ProductDetailPageView.d.ts +1 -1
- package/dist/features/products/components/ProductDetailPageView.js +116 -25
- package/dist/features/products/components/ProductFilters.d.ts +6 -11
- package/dist/features/products/components/ProductFilters.js +5 -3
- package/dist/features/products/components/ProductGrid.d.ts +8 -2
- package/dist/features/products/components/ProductGrid.js +20 -5
- package/dist/features/products/components/ProductTabsShell.d.ts +3 -11
- package/dist/features/products/components/ProductTabsShell.js +14 -14
- package/dist/features/products/components/ProductsIndexListing.js +73 -9
- package/dist/features/products/components/ProductsIndexPageView.d.ts +6 -1
- package/dist/features/products/components/ProductsIndexPageView.js +39 -6
- package/dist/features/products/components/RelatedProductsCarousel.d.ts +7 -0
- package/dist/features/products/components/RelatedProductsCarousel.js +11 -0
- package/dist/features/products/components/index.d.ts +1 -0
- package/dist/features/products/components/index.js +1 -0
- package/dist/features/products/hooks/useProducts.js +16 -0
- package/dist/features/products/repository/products.repository.d.ts +8 -0
- package/dist/features/products/repository/products.repository.js +2 -0
- package/dist/features/products/schemas/index.d.ts +8 -8
- package/dist/features/products/types/index.d.ts +12 -0
- package/dist/features/promotions/components/CouponsIndexListing.d.ts +9 -0
- package/dist/features/promotions/components/CouponsIndexListing.js +86 -0
- package/dist/features/promotions/components/PromotionsView.d.ts +11 -5
- package/dist/features/promotions/components/PromotionsView.js +6 -1
- package/dist/features/promotions/components/index.d.ts +4 -2
- package/dist/features/promotions/components/index.js +2 -1
- package/dist/features/reviews/components/ReviewDetailPageView.d.ts +4 -0
- package/dist/features/reviews/components/ReviewDetailPageView.js +20 -0
- package/dist/features/reviews/components/ReviewDetailShell.d.ts +7 -0
- package/dist/features/reviews/components/ReviewDetailShell.js +80 -0
- package/dist/features/reviews/components/ReviewFilters.d.ts +3 -3
- package/dist/features/reviews/components/ReviewFilters.js +5 -4
- package/dist/features/reviews/components/ReviewsIndexListing.d.ts +4 -3
- package/dist/features/reviews/components/ReviewsIndexListing.js +35 -51
- package/dist/features/reviews/components/ReviewsIndexPageView.d.ts +6 -1
- package/dist/features/reviews/components/ReviewsIndexPageView.js +49 -3
- package/dist/features/reviews/components/ReviewsList.js +9 -1
- package/dist/features/reviews/components/index.d.ts +1 -0
- package/dist/features/reviews/hooks/useReviews.js +15 -1
- package/dist/features/reviews/types/index.d.ts +6 -1
- package/dist/features/seller/components/SellerSidebar.d.ts +8 -4
- package/dist/features/seller/components/SellerSidebar.js +6 -4
- package/dist/features/seller/components/index.d.ts +30 -0
- package/dist/features/seller/components/index.js +17 -0
- package/dist/features/seller/hooks/useSellerStore.d.ts +2 -0
- package/dist/features/seller/hooks/useSellerStore.js +2 -0
- package/dist/features/seller/permission-map.d.ts +4 -2
- package/dist/features/seller/permission-map.js +16 -14
- package/dist/features/seller/schemas/index.d.ts +2 -2
- package/dist/features/stores/api/[storeSlug]/reviews/route.d.ts +1 -1
- package/dist/features/stores/api/[storeSlug]/reviews/route.js +24 -19
- package/dist/features/stores/components/InteractiveStoreCard.d.ts +0 -5
- package/dist/features/stores/components/InteractiveStoreCard.js +9 -9
- package/dist/features/stores/components/StoreAuctionsListing.js +27 -9
- package/dist/features/stores/components/StoreDetailLayoutView.js +2 -0
- package/dist/features/stores/components/StoreFilters.d.ts +5 -0
- package/dist/features/stores/components/StoreFilters.js +20 -0
- package/dist/features/stores/components/StoreHeader.js +2 -2
- package/dist/features/stores/components/StorePreOrdersListing.d.ts +5 -0
- package/dist/features/stores/components/StorePreOrdersListing.js +40 -0
- package/dist/features/stores/components/StorePreOrdersPageView.d.ts +4 -0
- package/dist/features/stores/components/StorePreOrdersPageView.js +21 -0
- package/dist/features/stores/components/StoreProductsListing.js +21 -11
- package/dist/features/stores/components/StoreReviewsListing.js +2 -7
- package/dist/features/stores/components/StoresIndexListing.js +42 -8
- package/dist/features/stores/components/StoresIndexPageView.d.ts +6 -1
- package/dist/features/stores/components/StoresIndexPageView.js +9 -2
- package/dist/features/stores/components/index.d.ts +3 -0
- package/dist/features/stores/components/index.js +1 -0
- package/dist/features/stores/hooks/useStores.d.ts +7 -1
- package/dist/features/stores/hooks/useStores.js +16 -3
- package/dist/features/stores/schemas/index.d.ts +2 -2
- package/dist/features/stores/types/index.d.ts +3 -0
- package/dist/features/wishlist/hooks/useGuestWishlist.d.ts +20 -0
- package/dist/features/wishlist/hooks/useGuestWishlist.js +49 -0
- package/dist/features/wishlist/hooks/useWishlistCount.d.ts +7 -0
- package/dist/features/wishlist/hooks/useWishlistCount.js +31 -0
- package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +56 -0
- package/dist/features/wishlist/hooks/useWishlistWithGuest.js +57 -0
- package/dist/features/wishlist/index.d.ts +3 -0
- package/dist/features/wishlist/index.js +3 -0
- package/dist/features/wishlist/utils/guest-wishlist.d.ts +22 -0
- package/dist/features/wishlist/utils/guest-wishlist.js +70 -0
- package/dist/index.d.ts +50 -1
- package/dist/index.js +63 -1
- package/dist/next/routing/route-map.d.ts +70 -36
- package/dist/next/routing/route-map.js +30 -22
- package/dist/seed/addresses-seed-data.js +62 -261
- package/dist/seed/beyblade-seed-data.d.ts +7 -0
- package/dist/seed/beyblade-seed-data.js +947 -0
- package/dist/seed/bids-seed-data.d.ts +10 -2
- package/dist/seed/bids-seed-data.js +220 -1071
- package/dist/seed/blog-posts-seed-data.d.ts +2 -2
- package/dist/seed/blog-posts-seed-data.js +455 -117
- package/dist/seed/cart-seed-data.d.ts +9 -9
- package/dist/seed/cart-seed-data.js +73 -74
- package/dist/seed/coupons-seed-data.d.ts +3 -4
- package/dist/seed/coupons-seed-data.js +3 -509
- package/dist/seed/events-seed-data.d.ts +2 -2
- package/dist/seed/events-seed-data.js +315 -476
- package/dist/seed/faq-seed-data.d.ts +18 -41
- package/dist/seed/faq-seed-data.js +1059 -1172
- package/dist/seed/hot-wheels-seed-data.d.ts +7 -0
- package/dist/seed/hot-wheels-seed-data.js +1365 -0
- package/dist/seed/index.d.ts +6 -1
- package/dist/seed/index.js +6 -1
- package/dist/seed/pokemon-carousel-slides-seed-data.d.ts +4 -2
- package/dist/seed/pokemon-carousel-slides-seed-data.js +152 -268
- package/dist/seed/pokemon-categories-seed-data.d.ts +18 -21
- package/dist/seed/pokemon-categories-seed-data.js +424 -1004
- package/dist/seed/pokemon-coupons-seed-data.d.ts +6 -0
- package/dist/seed/pokemon-coupons-seed-data.js +465 -0
- package/dist/seed/pokemon-homepage-sections-seed-data.d.ts +3 -2
- package/dist/seed/pokemon-homepage-sections-seed-data.js +67 -289
- package/dist/seed/pokemon-products-seed-data.js +662 -0
- package/dist/seed/pokemon-seed-bundle.d.ts +32 -11
- package/dist/seed/pokemon-seed-bundle.js +41 -11
- package/dist/seed/pokemon-stores-seed-data.d.ts +2 -3
- package/dist/seed/pokemon-stores-seed-data.js +56 -31
- package/dist/seed/pokemon-users-seed-data.d.ts +2 -2
- package/dist/seed/pokemon-users-seed-data.js +245 -261
- package/dist/seed/reviews-seed-data.d.ts +17 -2
- package/dist/seed/reviews-seed-data.js +519 -483
- package/dist/seed/site-settings-seed-data.js +14 -14
- package/dist/seed/store-addresses-seed-data.js +68 -50
- package/dist/seed/transformers-seed-data.d.ts +7 -0
- package/dist/seed/transformers-seed-data.js +510 -0
- package/dist/seed/wishlists-seed-data.d.ts +5 -1
- package/dist/seed/wishlists-seed-data.js +82 -4
- package/dist/server.d.ts +1 -0
- package/dist/server.js +2 -0
- package/dist/tokens/index.d.ts +6 -0
- package/dist/tokens/index.js +2 -0
- package/dist/ui/components/BaseListingCard.js +24 -26
- package/dist/ui/components/BaseListingCard.style.css +5 -5
- package/dist/ui/components/HorizontalScroller.d.ts +1 -1
- package/dist/ui/components/HorizontalScroller.js +19 -5
- package/dist/ui/components/SideDrawer.style.css +3 -11
- package/dist/ui/rich-text/RichText.js +19 -1
- 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 {
|
|
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
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
10
|
-
sorts:
|
|
11
|
-
page
|
|
12
|
-
pageSize
|
|
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,
|
|
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
|
|
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 = {},
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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 ${
|
|
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;
|