@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
|
@@ -9,12 +9,16 @@ interface ProductCardProps<T extends ProductItem = ProductItem> {
|
|
|
9
9
|
onClick?: (product: T) => void;
|
|
10
10
|
onAddToWishlist?: (productId: string) => void;
|
|
11
11
|
isWishlisted?: boolean;
|
|
12
|
+
onAddToCart?: (product: T) => void;
|
|
13
|
+
onBuyNow?: (product: T) => void;
|
|
12
14
|
className?: string;
|
|
13
15
|
}
|
|
14
|
-
export declare function ProductCard<T extends ProductItem = ProductItem>({ product, href, onClick, onAddToWishlist, isWishlisted, className, }: ProductCardProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
export declare function ProductCard<T extends ProductItem = ProductItem>({ product, href, onClick, onAddToWishlist, isWishlisted, onAddToCart, onBuyNow, className, }: ProductCardProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
15
17
|
export interface ProductCardContext<T extends ProductItem = ProductItem> {
|
|
16
18
|
onClick?: (product: T) => void;
|
|
17
19
|
onWishlistToggle?: (productId: string) => void;
|
|
20
|
+
onAddToCart?: (product: T) => void;
|
|
21
|
+
onBuyNow?: (product: T) => void;
|
|
18
22
|
isWishlisted: boolean;
|
|
19
23
|
}
|
|
20
24
|
interface ProductGridProps<T extends ProductItem = ProductItem> {
|
|
@@ -36,6 +40,8 @@ interface ProductGridProps<T extends ProductItem = ProductItem> {
|
|
|
36
40
|
/** When provided, each card is wrapped in a Link using this href generator. */
|
|
37
41
|
getProductHref?: (product: T) => string;
|
|
38
42
|
onWishlistToggle?: (productId: string) => void;
|
|
43
|
+
onAddToCart?: (product: T) => void;
|
|
44
|
+
onBuyNow?: (product: T) => void;
|
|
39
45
|
wishlistedIds?: Set<string>;
|
|
40
46
|
/** Text shown when the list is empty and no `emptySlot` is provided. */
|
|
41
47
|
emptyLabel?: string;
|
|
@@ -69,5 +75,5 @@ interface ProductGridProps<T extends ProductItem = ProductItem> {
|
|
|
69
75
|
*/
|
|
70
76
|
view?: ViewMode;
|
|
71
77
|
}
|
|
72
|
-
export declare function ProductGrid<T extends ProductItem = ProductItem>({ products, renderCard, onProductClick, getProductHref, onWishlistToggle, wishlistedIds, emptyLabel, emptySlot, headerSlot, footerSlot, className, slots, total, currentPage, totalPages, view, }: ProductGridProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
78
|
+
export declare function ProductGrid<T extends ProductItem = ProductItem>({ products, renderCard, onProductClick, getProductHref, onWishlistToggle, onAddToCart, onBuyNow, wishlistedIds, emptyLabel, emptySlot, headerSlot, footerSlot, className, slots, total, currentPage, totalPages, view, }: ProductGridProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
73
79
|
export {};
|
|
@@ -7,7 +7,7 @@ import { formatCurrency } from "../../../utils/number.formatter";
|
|
|
7
7
|
import { getDefaultCurrency, getDefaultCurrencySymbol } from "../../../core/baseline-resolver";
|
|
8
8
|
import { normalizeRichTextHtml } from "../../../utils/string.formatter";
|
|
9
9
|
import { safeDisplayName } from "../../../security";
|
|
10
|
-
export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlisted, className = "", }) {
|
|
10
|
+
export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlisted, onAddToCart, onBuyNow, className = "", }) {
|
|
11
11
|
const discount = product.originalPrice && product.originalPrice > product.price
|
|
12
12
|
? Math.round(((product.originalPrice - product.price) / product.originalPrice) *
|
|
13
13
|
100)
|
|
@@ -17,7 +17,18 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
|
|
|
17
17
|
: undefined, onClick: onClick && !href ? () => onClick(product) : undefined, className: `group relative flex h-full flex-col overflow-hidden rounded-xl border border-zinc-200 bg-white shadow-sm transition hover:shadow-md dark:border-slate-700 dark:bg-slate-900 ${onClick || href ? "cursor-pointer" : ""} ${className}`, children: [_jsxs(Div, { className: "relative aspect-square overflow-hidden bg-neutral-100 dark:bg-slate-800", children: [product.mainImage ? (_jsx(Div, { role: "img", "aria-label": product.title, className: "h-full w-full bg-center bg-cover transition-transform duration-300 group-hover:scale-105", style: { backgroundImage: `url(${product.mainImage})` } })) : (_jsx(Div, { className: "h-full w-full bg-neutral-200 dark:bg-slate-700" })), discount && (_jsxs(Span, { className: "absolute left-2 top-2 rounded-full bg-red-500 px-2 py-0.5 text-xs font-bold text-white", children: ["-", discount, "%"] })), product.isAuction && (_jsx(Span, { className: "absolute right-2 top-2 rounded-full bg-amber-500 px-2 py-0.5 text-xs font-bold text-white", children: "Auction" })), onAddToWishlist && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: (e) => {
|
|
18
18
|
e.stopPropagation();
|
|
19
19
|
onAddToWishlist(product.id);
|
|
20
|
-
}, "aria-label": isWishlisted ? "Remove from wishlist" : "Add to wishlist", className: "absolute bottom-2 right-2 flex h-8 w-8 items-center justify-center rounded-full bg-white/90 dark:bg-slate-800/90 text-neutral-600 dark:text-zinc-300 shadow transition hover:bg-white dark:hover:bg-slate-700 hover:text-red-500", children: isWishlisted ? "♥" : "♡" }))] }), _jsxs(Div, { className: "flex flex-1 flex-col border-t border-zinc-200/80 bg-zinc-50 p-3 dark:border-slate-700/80 dark:bg-slate-900", children: [_jsx(Text, { className: `${THEME_CONSTANTS.utilities.textClamp2} text-sm font-medium text-zinc-950 dark:text-white`, children: product.title }), product.description && (_jsx(RichText, { html: normalizeRichTextHtml(product.description), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: `mt-1 ${THEME_CONSTANTS.utilities.textClamp2} text-xs text-zinc-600 dark:text-zinc-400` })),
|
|
20
|
+
}, "aria-label": isWishlisted ? "Remove from wishlist" : "Add to wishlist", className: "absolute bottom-2 right-2 flex h-8 w-8 items-center justify-center rounded-full bg-white/90 dark:bg-slate-800/90 text-neutral-600 dark:text-zinc-300 shadow transition hover:bg-white dark:hover:bg-slate-700 hover:text-red-500", children: isWishlisted ? "♥" : "♡" }))] }), _jsxs(Div, { className: "flex flex-1 flex-col border-t border-zinc-200/80 bg-zinc-50 p-3 dark:border-slate-700/80 dark:bg-slate-900", children: [_jsx(Text, { className: `${THEME_CONSTANTS.utilities.textClamp2} text-sm font-medium text-zinc-950 dark:text-white`, children: product.title }), product.description && (_jsx(RichText, { html: normalizeRichTextHtml(product.description), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: `mt-1 ${THEME_CONSTANTS.utilities.textClamp2} text-xs text-zinc-600 dark:text-zinc-400` })), (() => {
|
|
21
|
+
const seller = safeDisplayName(product.sellerName, "");
|
|
22
|
+
return seller ? (_jsxs(Text, { className: "mt-0.5 text-xs text-zinc-500 dark:text-zinc-400", children: ["by ", seller] })) : null;
|
|
23
|
+
})(), _jsxs(Div, { className: "mt-2 flex items-baseline gap-2", children: [_jsx(Span, { className: "font-semibold text-zinc-950 dark:text-white", children: formatCurrency(product.price, getDefaultCurrency()) }), product.originalPrice && (_jsx(Span, { className: "text-xs text-zinc-500 line-through dark:text-zinc-400", children: formatCurrency(product.originalPrice, getDefaultCurrency()) }))] }), product.rating !== undefined && (_jsxs(Row, { className: "mt-1 gap-1", children: [_jsx(Span, { className: "text-xs text-yellow-500", children: "\u2605" }), _jsxs(Span, { className: "text-xs text-neutral-500 dark:text-zinc-400", children: [product.rating.toFixed(1), product.reviewCount ? ` (${product.reviewCount})` : ""] })] })), (onAddToCart || onBuyNow) && (_jsxs(Row, { className: "mt-3 gap-1.5", children: [onBuyNow && (_jsx(Button, { type: "button", onClick: (e) => {
|
|
24
|
+
e.stopPropagation();
|
|
25
|
+
e.preventDefault();
|
|
26
|
+
onBuyNow(product);
|
|
27
|
+
}, className: "flex-1 rounded-lg bg-primary py-1.5 text-xs font-semibold text-white hover:bg-primary-600 transition-colors", children: "\u26A1 Buy Now" })), onAddToCart && (_jsx(Button, { type: "button", onClick: (e) => {
|
|
28
|
+
e.stopPropagation();
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
onAddToCart(product);
|
|
31
|
+
}, className: "flex-1 rounded-lg border border-primary py-1.5 text-xs font-semibold text-primary hover:bg-primary/5 transition-colors", children: "\uD83D\uDED2 Add to Cart" }))] }))] })] }));
|
|
21
32
|
if (href) {
|
|
22
33
|
return (_jsx(Link, { href: href, className: "block h-full", children: cardBody }));
|
|
23
34
|
}
|
|
@@ -46,7 +57,7 @@ function ProductListRow({ product, onClick, onAddToWishlist, isWishlisted, }) {
|
|
|
46
57
|
onAddToWishlist(product.id);
|
|
47
58
|
}, "aria-label": isWishlisted ? "Remove from wishlist" : "Add to wishlist", className: "flex-shrink-0 w-[36px] h-[36px] flex items-center justify-center rounded-lg text-neutral-400 hover:text-red-500 transition-colors", children: isWishlisted ? "♥" : "♡" }))] }));
|
|
48
59
|
}
|
|
49
|
-
export function ProductGrid({ products, renderCard, onProductClick, getProductHref, onWishlistToggle, wishlistedIds, emptyLabel = "No products found", emptySlot, headerSlot, footerSlot, className = "", slots, total = 0, currentPage = 1, totalPages = 1, view = "card", }) {
|
|
60
|
+
export function ProductGrid({ products, renderCard, onProductClick, getProductHref, onWishlistToggle, onAddToCart, onBuyNow, wishlistedIds, emptyLabel = "No products found", emptySlot, headerSlot, footerSlot, className = "", slots, total = 0, currentPage = 1, totalPages = 1, view = "card", }) {
|
|
50
61
|
const isEmpty = products.length === 0;
|
|
51
62
|
// Slot resolution: explicit props win over `slots` object
|
|
52
63
|
const resolvedHeader = headerSlot ??
|
|
@@ -73,12 +84,14 @@ export function ProductGrid({ products, renderCard, onProductClick, getProductHr
|
|
|
73
84
|
const ctx = {
|
|
74
85
|
onClick: onProductClick,
|
|
75
86
|
onWishlistToggle,
|
|
87
|
+
onAddToCart,
|
|
88
|
+
onBuyNow,
|
|
76
89
|
isWishlisted: wishlistedIds?.has(p.id) ?? false,
|
|
77
90
|
};
|
|
78
91
|
const cardRenderer = renderCard ?? slots?.renderCard;
|
|
79
92
|
return cardRenderer ? (_jsx(React.Fragment, { children: cardRenderer === renderCard
|
|
80
93
|
? renderCard(p, ctx)
|
|
81
|
-
: slots.renderCard(p, i) }, p.id)) : (_jsx(ProductCard, { product: p, href: getProductHref ? getProductHref(p) : undefined, onClick: onProductClick, onAddToWishlist: onWishlistToggle, isWishlisted: ctx.isWishlisted }, p.id));
|
|
94
|
+
: slots.renderCard(p, i) }, p.id)) : (_jsx(ProductCard, { product: p, href: getProductHref ? getProductHref(p) : undefined, onClick: onProductClick, onAddToWishlist: onWishlistToggle, onAddToCart: onAddToCart, onBuyNow: onBuyNow, isWishlisted: ctx.isWishlisted }, p.id));
|
|
82
95
|
}) }));
|
|
83
96
|
}
|
|
84
97
|
const gridClass = GRID_CLASSES.card;
|
|
@@ -86,12 +99,14 @@ export function ProductGrid({ products, renderCard, onProductClick, getProductHr
|
|
|
86
99
|
const ctx = {
|
|
87
100
|
onClick: onProductClick,
|
|
88
101
|
onWishlistToggle,
|
|
102
|
+
onAddToCart,
|
|
103
|
+
onBuyNow,
|
|
89
104
|
isWishlisted: wishlistedIds?.has(p.id) ?? false,
|
|
90
105
|
};
|
|
91
106
|
const cardRenderer = renderCard ?? slots?.renderCard;
|
|
92
107
|
return cardRenderer ? (_jsx(React.Fragment, { children: cardRenderer === renderCard
|
|
93
108
|
? renderCard(p, ctx)
|
|
94
|
-
: slots.renderCard(p, i) }, p.id)) : (_jsx(ProductCard, { product: p, href: getProductHref ? getProductHref(p) : undefined, onClick: onProductClick, onAddToWishlist: onWishlistToggle, isWishlisted: ctx.isWishlisted }, p.id));
|
|
109
|
+
: slots.renderCard(p, i) }, p.id)) : (_jsx(ProductCard, { product: p, href: getProductHref ? getProductHref(p) : undefined, onClick: onProductClick, onAddToWishlist: onWishlistToggle, onAddToCart: onAddToCart, onBuyNow: onBuyNow, isWishlisted: ctx.isWishlisted }, p.id));
|
|
95
110
|
}) }));
|
|
96
111
|
};
|
|
97
112
|
return (_jsxs(Div, { children: [resolvedHeader, isEmpty
|
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
export interface ProductTabsShellProps {
|
|
3
|
-
/** Pre-rendered description content (server RSC node) */
|
|
4
3
|
descriptionContent?: React.ReactNode;
|
|
5
|
-
/** Pre-rendered specifications content (server RSC node) */
|
|
6
4
|
specsContent?: React.ReactNode;
|
|
7
|
-
|
|
5
|
+
ingredientsContent?: React.ReactNode;
|
|
6
|
+
howToUseContent?: React.ReactNode;
|
|
8
7
|
reviewsContent?: React.ReactNode;
|
|
9
8
|
className?: string;
|
|
10
9
|
}
|
|
11
|
-
|
|
12
|
-
* RSC-safe tab shell for product detail pages.
|
|
13
|
-
*
|
|
14
|
-
* Accepts pre-rendered ReactNode children instead of render functions so the
|
|
15
|
-
* component can be used inside a server component tree. (Functions cannot cross
|
|
16
|
-
* the RSC serialisation boundary to a "use client" component.)
|
|
17
|
-
*/
|
|
18
|
-
export declare function ProductTabsShell({ descriptionContent, specsContent, reviewsContent, className, }: ProductTabsShellProps): import("react/jsx-runtime").JSX.Element | null;
|
|
10
|
+
export declare function ProductTabsShell({ descriptionContent, specsContent, ingredientsContent, howToUseContent, reviewsContent, className, }: ProductTabsShellProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
|
-
const
|
|
4
|
+
const ALL_TABS = [
|
|
5
5
|
{ id: "description", label: "Description" },
|
|
6
6
|
{ id: "specs", label: "Specifications" },
|
|
7
|
+
{ id: "ingredients", label: "Ingredients" },
|
|
8
|
+
{ id: "howToUse", label: "How to Use" },
|
|
7
9
|
{ id: "reviews", label: "Reviews" },
|
|
8
10
|
];
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const visibleTabs =
|
|
18
|
-
(t.id === "specs" && specsContent != null) ||
|
|
19
|
-
(t.id === "reviews" && reviewsContent != null));
|
|
11
|
+
export function ProductTabsShell({ descriptionContent, specsContent, ingredientsContent, howToUseContent, reviewsContent, className = "", }) {
|
|
12
|
+
const contentMap = {
|
|
13
|
+
description: descriptionContent,
|
|
14
|
+
specs: specsContent,
|
|
15
|
+
ingredients: ingredientsContent,
|
|
16
|
+
howToUse: howToUseContent,
|
|
17
|
+
reviews: reviewsContent,
|
|
18
|
+
};
|
|
19
|
+
const visibleTabs = ALL_TABS.filter((t) => contentMap[t.id] != null);
|
|
20
20
|
const [activeTab, setActiveTab] = useState(visibleTabs[0]?.id ?? "description");
|
|
21
21
|
if (visibleTabs.length === 0)
|
|
22
22
|
return null;
|
|
23
|
-
return (_jsxs("div", { className: className
|
|
23
|
+
return (_jsxs("div", { className: `mt-8 ${className}`, children: [_jsx("div", { className: "mb-6 flex gap-1 overflow-x-auto border-b border-zinc-200 dark:border-zinc-700 pb-px", children: visibleTabs.map((t) => (_jsx("button", { type: "button", onClick: () => setActiveTab(t.id), className: `flex-shrink-0 -mb-px pb-3 px-4 text-sm font-medium border-b-2 transition-colors ${activeTab === t.id
|
|
24
24
|
? "border-primary-500 text-primary-600 dark:text-primary-400"
|
|
25
|
-
: "border-transparent text-
|
|
25
|
+
: "border-transparent text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300"}`, children: t.label }, t.id))) }), _jsx("div", { children: visibleTabs.map((t) => (_jsx("div", { hidden: activeTab !== t.id, children: contentMap[t.id] }, t.id))) })] }));
|
|
26
26
|
}
|
|
@@ -1,27 +1,91 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback } from "react";
|
|
4
|
+
import { Search, SlidersHorizontal, LayoutGrid, List, X } from "lucide-react";
|
|
5
|
+
import { useRouter } from "next/navigation";
|
|
3
6
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
4
7
|
import { useProducts } from "../hooks/useProducts";
|
|
5
|
-
import {
|
|
8
|
+
import { Pagination, SortDropdown } from "../../../ui";
|
|
6
9
|
import { ROUTES } from "../../../next";
|
|
7
|
-
import { ProductGrid, ProductFilters } from ".";
|
|
10
|
+
import { ProductGrid, ProductFilters, PRODUCT_PUBLIC_SORT_OPTIONS } from ".";
|
|
11
|
+
import { useSession } from "../../../react/contexts/SessionContext";
|
|
12
|
+
import { useWishlistWithGuest } from "../../wishlist/hooks/useWishlistWithGuest";
|
|
13
|
+
import { apiClient } from "../../../http";
|
|
14
|
+
import { useCategoryTree, categoriesToFacetOptions } from "../../categories/hooks/useCategoryTree";
|
|
8
15
|
export function ProductsIndexListing({ initialData }) {
|
|
16
|
+
const router = useRouter();
|
|
9
17
|
const table = useUrlTable({ defaults: { pageSize: "24", sort: "-createdAt" } });
|
|
18
|
+
const [searchInput, setSearchInput] = useState(table.get("q") || "");
|
|
19
|
+
const [filterOpen, setFilterOpen] = useState(false);
|
|
20
|
+
const [view, setView] = useState(table.get("view") || "card");
|
|
21
|
+
const { user } = useSession();
|
|
22
|
+
const wl = useWishlistWithGuest(user?.uid ?? null);
|
|
23
|
+
const { categories } = useCategoryTree();
|
|
24
|
+
const categoryOptions = categoriesToFacetOptions(categories);
|
|
10
25
|
const params = {
|
|
11
26
|
q: table.get("q") || undefined,
|
|
12
27
|
category: table.get("category") || undefined,
|
|
13
28
|
minPrice: table.get("minPrice") ? Number(table.get("minPrice")) : undefined,
|
|
14
29
|
maxPrice: table.get("maxPrice") ? Number(table.get("maxPrice")) : undefined,
|
|
15
30
|
condition: table.get("condition") || undefined,
|
|
16
|
-
|
|
31
|
+
brand: table.get("brand") || undefined,
|
|
32
|
+
storeId: table.get("storeId") || undefined,
|
|
33
|
+
freeShipping: table.get("freeShipping") === "true" ? true : undefined,
|
|
34
|
+
sort: table.get("sort") || "-createdAt",
|
|
17
35
|
page: table.getNumber("page", 1),
|
|
18
36
|
perPage: table.getNumber("pageSize", 24),
|
|
19
37
|
isAuction: false,
|
|
20
38
|
};
|
|
21
39
|
const { products, total, totalPages, page, isLoading } = useProducts(params, { initialData });
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
40
|
+
const commitSearch = useCallback(() => {
|
|
41
|
+
table.set("q", searchInput.trim());
|
|
42
|
+
table.setPage(1);
|
|
43
|
+
}, [searchInput, table]);
|
|
44
|
+
const handleSearchKeyDown = (e) => {
|
|
45
|
+
if (e.key === "Enter")
|
|
46
|
+
commitSearch();
|
|
47
|
+
};
|
|
48
|
+
const handleViewToggle = (next) => {
|
|
49
|
+
setView(next);
|
|
50
|
+
table.set("view", next);
|
|
51
|
+
};
|
|
52
|
+
const closeFilters = () => setFilterOpen(false);
|
|
53
|
+
const handleWishlistToggle = useCallback(async (productId) => {
|
|
54
|
+
const isWishlisted = wl.wishlistedIds?.has(productId) ?? false;
|
|
55
|
+
if (wl.isGuest) {
|
|
56
|
+
const guestWl = wl.guestWishlist;
|
|
57
|
+
if (isWishlisted)
|
|
58
|
+
guestWl?.remove(productId, "product");
|
|
59
|
+
else
|
|
60
|
+
guestWl?.add(productId, "product");
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
if (isWishlisted)
|
|
64
|
+
await apiClient.delete(`/api/user/wishlist/${productId}`);
|
|
65
|
+
else
|
|
66
|
+
await apiClient.post("/api/user/wishlist", { productId });
|
|
67
|
+
}
|
|
68
|
+
}, [wl]);
|
|
69
|
+
const handleAddToCart = useCallback(async (product) => {
|
|
70
|
+
try {
|
|
71
|
+
await apiClient.post("/api/cart", { productId: product.id, quantity: 1 });
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// silently ignore cart errors on listing page
|
|
75
|
+
}
|
|
76
|
+
}, []);
|
|
77
|
+
const handleBuyNow = useCallback(async (product) => {
|
|
78
|
+
try {
|
|
79
|
+
await apiClient.post("/api/cart", { productId: product.id, quantity: 1 });
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// silently ignore cart errors; still navigate
|
|
83
|
+
}
|
|
84
|
+
router.push("/cart");
|
|
85
|
+
}, [router]);
|
|
86
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx("div", { className: `sticky top-0 z-20 border-b border-zinc-200 dark:border-slate-700 bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm py-2.5 px-4`, children: _jsxs("div", { className: "flex items-center gap-2.5 max-w-full", children: [_jsxs("button", { type: "button", onClick: () => setFilterOpen(true), className: "flex shrink-0 items-center gap-2 rounded-lg border border-zinc-300 dark:border-slate-600 px-3.5 py-2 text-sm font-medium text-zinc-700 dark:text-zinc-200 hover:bg-zinc-50 dark:hover:bg-slate-800 transition-colors", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), _jsx("span", { className: "hidden sm:inline", children: "Filters" })] }), _jsxs("div", { className: "flex flex-1 items-center overflow-hidden rounded-lg border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-900", children: [_jsx("input", { type: "text", value: searchInput, onChange: (e) => setSearchInput(e.target.value), onKeyDown: handleSearchKeyDown, placeholder: "Search products...", className: "min-w-0 flex-1 bg-transparent px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 outline-none" }), _jsx("button", { type: "button", onClick: commitSearch, className: "flex shrink-0 items-center justify-center px-3 py-2 text-zinc-400 hover:text-primary dark:hover:text-primary-400 transition-colors", "aria-label": "Search", children: _jsx(Search, { className: "h-4 w-4" }) })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1.5 text-sm text-zinc-500 dark:text-zinc-400", children: [_jsx("span", { className: "hidden md:inline whitespace-nowrap", children: "Sort by" }), _jsx(SortDropdown, { value: table.get("sort") || "-createdAt", onChange: (v) => { table.set("sort", v); table.setPage(1); }, options: PRODUCT_PUBLIC_SORT_OPTIONS })] }), _jsxs("div", { className: "flex shrink-0 items-center rounded-lg border border-zinc-300 dark:border-slate-600 overflow-hidden", children: [_jsx("button", { type: "button", onClick: () => handleViewToggle("card"), "aria-label": "Grid view", className: `p-2 transition-colors ${view === "card"
|
|
87
|
+
? "bg-primary text-white"
|
|
88
|
+
: "text-zinc-500 hover:bg-zinc-50 dark:hover:bg-slate-800 dark:text-zinc-400"}`, children: _jsx(LayoutGrid, { className: "h-4 w-4" }) }), _jsx("button", { type: "button", onClick: () => handleViewToggle("list"), "aria-label": "List view", className: `p-2 transition-colors ${view === "list"
|
|
89
|
+
? "bg-primary text-white"
|
|
90
|
+
: "text-zinc-500 hover:bg-zinc-50 dark:hover:bg-slate-800 dark:text-zinc-400"}`, children: _jsx(List, { className: "h-4 w-4" }) })] })] }) }), _jsxs("div", { className: "py-6", children: [isLoading ? (_jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4", children: Array.from({ length: 10 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-square bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-3 space-y-2", children: [_jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" }), _jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-1/3" })] })] }, i))) })) : (_jsx(ProductGrid, { products: products, getProductHref: (p) => String(ROUTES.PUBLIC.PRODUCT_DETAIL(p.slug || p.id)), view: view, onWishlistToggle: handleWishlistToggle, wishlistedIds: wl.wishlistedIds, onAddToCart: handleAddToCart, onBuyNow: handleBuyNow })), totalPages > 1 && (_jsx("div", { className: "mt-8 flex justify-center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }))] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: closeFilters }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsxs("span", { className: "flex items-center gap-2 text-base font-semibold text-zinc-900 dark:text-zinc-100", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Filters"] }), _jsx("button", { type: "button", onClick: closeFilters, "aria-label": "Close filters", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4", children: _jsx(ProductFilters, { table: table, currencyPrefix: "\u20B9", categoryOptions: categoryOptions }) }), _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" }) })] })] }))] }));
|
|
27
91
|
}
|
|
@@ -1 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
type SearchParams = Record<string, string | string[]>;
|
|
2
|
+
export interface ProductsIndexPageViewProps {
|
|
3
|
+
searchParams?: SearchParams;
|
|
4
|
+
}
|
|
5
|
+
export declare function ProductsIndexPageView({ searchParams }: ProductsIndexPageViewProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
6
|
+
export {};
|
|
@@ -1,17 +1,50 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { productRepository } from "../../../repositories";
|
|
3
|
+
import { ROUTES } from "../../../constants";
|
|
3
4
|
import { Container, Heading, Main, Section } from "../../../ui";
|
|
4
5
|
import { AdSlot } from "../../homepage/components/AdSlot";
|
|
5
6
|
import { ProductsIndexListing } from "./ProductsIndexListing";
|
|
6
|
-
|
|
7
|
+
function sp(params, key) {
|
|
8
|
+
const v = params[key];
|
|
9
|
+
return Array.isArray(v) ? v[0] ?? "" : v ?? "";
|
|
10
|
+
}
|
|
11
|
+
function buildProductFilters(params) {
|
|
12
|
+
const parts = ["status==published", "isAuction==false"];
|
|
13
|
+
const condition = sp(params, "condition");
|
|
14
|
+
if (condition) {
|
|
15
|
+
const values = condition.split("|").filter(Boolean);
|
|
16
|
+
if (values.length === 1)
|
|
17
|
+
parts.push(`condition==${values[0]}`);
|
|
18
|
+
else if (values.length > 1)
|
|
19
|
+
parts.push(`condition==${values.join("|")}`);
|
|
20
|
+
}
|
|
21
|
+
const minPrice = sp(params, "minPrice");
|
|
22
|
+
const maxPrice = sp(params, "maxPrice");
|
|
23
|
+
if (minPrice)
|
|
24
|
+
parts.push(`price>=${minPrice}`);
|
|
25
|
+
if (maxPrice)
|
|
26
|
+
parts.push(`price<=${maxPrice}`);
|
|
27
|
+
const sellerId = sp(params, "seller");
|
|
28
|
+
if (sellerId)
|
|
29
|
+
parts.push(`sellerId==${sellerId}`);
|
|
30
|
+
const freeShipping = sp(params, "freeShipping");
|
|
31
|
+
if (freeShipping === "true")
|
|
32
|
+
parts.push("freeShipping==true");
|
|
33
|
+
return parts.join(",");
|
|
34
|
+
}
|
|
35
|
+
export async function ProductsIndexPageView({ searchParams = {} }) {
|
|
36
|
+
const sort = sp(searchParams, "sort") || "-createdAt";
|
|
37
|
+
const page = Number(sp(searchParams, "page")) || 1;
|
|
38
|
+
const pageSize = Number(sp(searchParams, "pageSize")) || 24;
|
|
39
|
+
const filters = buildProductFilters(searchParams);
|
|
7
40
|
const result = await productRepository
|
|
8
41
|
.list({
|
|
9
|
-
filters
|
|
10
|
-
sorts:
|
|
11
|
-
page
|
|
12
|
-
pageSize
|
|
42
|
+
filters,
|
|
43
|
+
sorts: sort,
|
|
44
|
+
page,
|
|
45
|
+
pageSize,
|
|
13
46
|
})
|
|
14
47
|
.catch(() => null);
|
|
15
48
|
const products = result ?? null;
|
|
16
|
-
return (
|
|
49
|
+
return (_jsxs(Main, { children: [_jsx(Section, { className: "pt-8 pb-4 border-b border-zinc-100 dark:border-slate-800", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 1, className: "text-3xl font-bold text-zinc-900 dark:text-zinc-100", children: "Products" }), _jsx("p", { className: "mt-1 text-sm text-zinc-500 dark:text-zinc-400", children: "Discover amazing products and deals" }), _jsx("div", { className: "mt-3", children: _jsx("a", { href: ROUTES.PUBLIC.AUCTIONS, className: "inline-flex items-center gap-1.5 rounded-full border border-primary/30 bg-primary/10 px-4 py-1.5 text-xs font-medium text-primary-700 dark:text-primary-400 hover:bg-primary/15 transition-colors", children: "\uD83C\uDFF7\uFE0F Looking for unique deals? Browse Auctions \u2192" }) })] }) }), _jsxs(Container, { size: "xl", className: "px-4", children: [_jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-4 mt-4" }), _jsx(ProductsIndexListing, { initialData: products }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] })] }));
|
|
17
50
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ProductItem } from "../types";
|
|
2
|
+
interface RelatedProductsCarouselProps {
|
|
3
|
+
items: ProductItem[];
|
|
4
|
+
title?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function RelatedProductsCarousel({ items, title, }: RelatedProductsCarouselProps): import("react/jsx-runtime").JSX.Element | null;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { ROUTES } from "../../../next";
|
|
4
|
+
import { THEME_CONSTANTS } from "../../../tokens";
|
|
5
|
+
import { SectionCarousel } from "../../homepage/components/SectionCarousel";
|
|
6
|
+
import { ProductCard } from "./ProductGrid";
|
|
7
|
+
export function RelatedProductsCarousel({ items, title = "You might also like", }) {
|
|
8
|
+
if (items.length === 0)
|
|
9
|
+
return null;
|
|
10
|
+
return (_jsx(SectionCarousel, { title: title, pillLabel: "Related Products", headingVariant: "editorial", items: items, isLoading: false, perView: THEME_CONSTANTS.carousel.perView.standard, keyExtractor: (item) => item.id, renderItem: (item) => (_jsx(ProductCard, { product: item, href: String(ROUTES.PUBLIC.PRODUCT_DETAIL(item.slug || item.id)) })) }));
|
|
11
|
+
}
|
|
@@ -36,3 +36,4 @@ export { ProductGalleryClient } from "./ProductGalleryClient";
|
|
|
36
36
|
export type { ProductGalleryClientProps } from "./ProductGalleryClient";
|
|
37
37
|
export { ProductTabsShell } from "./ProductTabsShell";
|
|
38
38
|
export type { ProductTabsShellProps } from "./ProductTabsShell";
|
|
39
|
+
export { RelatedProductsCarousel } from "./RelatedProductsCarousel";
|
|
@@ -18,3 +18,4 @@ export { MakeOfferForm } from "./MakeOfferForm";
|
|
|
18
18
|
export { RelatedProducts } from "./RelatedProducts";
|
|
19
19
|
export { ProductGalleryClient } from "./ProductGalleryClient";
|
|
20
20
|
export { ProductTabsShell } from "./ProductTabsShell";
|
|
21
|
+
export { RelatedProductsCarousel } from "./RelatedProductsCarousel";
|
|
@@ -21,8 +21,12 @@ export function useProducts(params = {}, opts) {
|
|
|
21
21
|
sp.set("inStock", String(params.inStock));
|
|
22
22
|
if (params.isAuction !== undefined)
|
|
23
23
|
sp.set("isAuction", String(params.isAuction));
|
|
24
|
+
if (params.isPreOrder !== undefined)
|
|
25
|
+
sp.set("isPreOrder", String(params.isPreOrder));
|
|
24
26
|
if (params.sellerId)
|
|
25
27
|
sp.set("sellerId", params.sellerId);
|
|
28
|
+
if (params.storeId)
|
|
29
|
+
sp.set("storeId", params.storeId);
|
|
26
30
|
if (params.sort)
|
|
27
31
|
sp.set("sorts", params.sort);
|
|
28
32
|
if (params.page)
|
|
@@ -31,6 +35,18 @@ export function useProducts(params = {}, opts) {
|
|
|
31
35
|
sp.set("pageSize", String(params.perPage));
|
|
32
36
|
if (params.featured !== undefined)
|
|
33
37
|
sp.set("featured", String(params.featured));
|
|
38
|
+
if (params.minBid !== undefined)
|
|
39
|
+
sp.set("minBid", String(params.minBid));
|
|
40
|
+
if (params.maxBid !== undefined)
|
|
41
|
+
sp.set("maxBid", String(params.maxBid));
|
|
42
|
+
if (params.dateFrom)
|
|
43
|
+
sp.set("dateFrom", params.dateFrom);
|
|
44
|
+
if (params.dateTo)
|
|
45
|
+
sp.set("dateTo", params.dateTo);
|
|
46
|
+
if (params.preOrderStatus)
|
|
47
|
+
sp.set("preOrderStatus", params.preOrderStatus);
|
|
48
|
+
if (params.freeShipping !== undefined)
|
|
49
|
+
sp.set("freeShipping", String(params.freeShipping));
|
|
34
50
|
const qs = sp.toString();
|
|
35
51
|
const query = useQuery({
|
|
36
52
|
queryKey: ["products", qs],
|
|
@@ -68,6 +68,10 @@ export declare class ProductRepository extends BaseRepository<ProductDocument> {
|
|
|
68
68
|
canFilter: boolean;
|
|
69
69
|
canSort: boolean;
|
|
70
70
|
};
|
|
71
|
+
storeId: {
|
|
72
|
+
canFilter: boolean;
|
|
73
|
+
canSort: boolean;
|
|
74
|
+
};
|
|
71
75
|
sellerName: {
|
|
72
76
|
canFilter: boolean;
|
|
73
77
|
canSort: boolean;
|
|
@@ -184,6 +188,10 @@ export declare class ProductRepository extends BaseRepository<ProductDocument> {
|
|
|
184
188
|
canFilter: boolean;
|
|
185
189
|
canSort: boolean;
|
|
186
190
|
};
|
|
191
|
+
freeShipping: {
|
|
192
|
+
canFilter: boolean;
|
|
193
|
+
canSort: boolean;
|
|
194
|
+
};
|
|
187
195
|
};
|
|
188
196
|
list(model: SieveModel, opts?: {
|
|
189
197
|
sellerId?: string;
|
|
@@ -314,6 +314,7 @@ ProductRepository.SIEVE_FIELDS = {
|
|
|
314
314
|
condition: { canFilter: true, canSort: false },
|
|
315
315
|
status: { canFilter: true, canSort: true },
|
|
316
316
|
sellerId: { canFilter: true, canSort: false },
|
|
317
|
+
storeId: { canFilter: true, canSort: false },
|
|
317
318
|
sellerName: { canFilter: true, canSort: true },
|
|
318
319
|
featured: { canFilter: true, canSort: false },
|
|
319
320
|
isAuction: { canFilter: true, canSort: false },
|
|
@@ -343,6 +344,7 @@ ProductRepository.SIEVE_FIELDS = {
|
|
|
343
344
|
features: { canFilter: true, canSort: false },
|
|
344
345
|
insurance: { canFilter: true, canSort: false },
|
|
345
346
|
currency: { canFilter: true, canSort: false },
|
|
347
|
+
freeShipping: { canFilter: true, canSort: false },
|
|
346
348
|
};
|
|
347
349
|
export class ProductsRepository extends ProductRepository {
|
|
348
350
|
}
|
|
@@ -213,10 +213,13 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
213
213
|
condition?: "new" | "used" | "refurbished" | "broken" | "like_new" | "good" | "fair" | "poor" | undefined;
|
|
214
214
|
inStock?: boolean | undefined;
|
|
215
215
|
brand?: string | undefined;
|
|
216
|
+
currentBid?: number | undefined;
|
|
217
|
+
startingBid?: number | undefined;
|
|
218
|
+
auctionEndDate?: string | Date | undefined;
|
|
219
|
+
preOrderDeliveryDate?: string | Date | undefined;
|
|
216
220
|
originalPrice?: number | undefined;
|
|
217
221
|
mainImage?: string | undefined;
|
|
218
222
|
isPromoted?: boolean | undefined;
|
|
219
|
-
currentBid?: number | undefined;
|
|
220
223
|
availableQuantity?: number | undefined;
|
|
221
224
|
sellerAvatar?: string | undefined;
|
|
222
225
|
listingType?: "fixed" | "standard" | "auction" | "pre-order" | undefined;
|
|
@@ -240,16 +243,13 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
240
243
|
discountPercent: number;
|
|
241
244
|
quantity: number;
|
|
242
245
|
}[] | undefined;
|
|
243
|
-
startingBid?: number | undefined;
|
|
244
246
|
bidCount?: number | undefined;
|
|
245
|
-
auctionEndDate?: string | Date | undefined;
|
|
246
247
|
reservePrice?: number | undefined;
|
|
247
248
|
buyNowPrice?: number | undefined;
|
|
248
249
|
minBidIncrement?: number | undefined;
|
|
249
250
|
autoExtendable?: boolean | undefined;
|
|
250
251
|
auctionExtensionMinutes?: number | undefined;
|
|
251
252
|
auctionShippingPaidBy?: "seller" | "winner" | undefined;
|
|
252
|
-
preOrderDeliveryDate?: string | Date | undefined;
|
|
253
253
|
preOrderDepositPercent?: number | undefined;
|
|
254
254
|
preOrderDepositAmount?: number | undefined;
|
|
255
255
|
preOrderMaxQuantity?: number | undefined;
|
|
@@ -304,10 +304,13 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
304
304
|
condition?: "new" | "used" | "refurbished" | "broken" | "like_new" | "good" | "fair" | "poor" | undefined;
|
|
305
305
|
inStock?: boolean | undefined;
|
|
306
306
|
brand?: string | undefined;
|
|
307
|
+
currentBid?: number | undefined;
|
|
308
|
+
startingBid?: number | undefined;
|
|
309
|
+
auctionEndDate?: string | Date | undefined;
|
|
310
|
+
preOrderDeliveryDate?: string | Date | undefined;
|
|
307
311
|
originalPrice?: number | undefined;
|
|
308
312
|
mainImage?: string | undefined;
|
|
309
313
|
isPromoted?: boolean | undefined;
|
|
310
|
-
currentBid?: number | undefined;
|
|
311
314
|
availableQuantity?: number | undefined;
|
|
312
315
|
sellerAvatar?: string | undefined;
|
|
313
316
|
listingType?: "fixed" | "standard" | "auction" | "pre-order" | undefined;
|
|
@@ -331,16 +334,13 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
331
334
|
discountPercent: number;
|
|
332
335
|
quantity: number;
|
|
333
336
|
}[] | undefined;
|
|
334
|
-
startingBid?: number | undefined;
|
|
335
337
|
bidCount?: number | undefined;
|
|
336
|
-
auctionEndDate?: string | Date | undefined;
|
|
337
338
|
reservePrice?: number | undefined;
|
|
338
339
|
buyNowPrice?: number | undefined;
|
|
339
340
|
minBidIncrement?: number | undefined;
|
|
340
341
|
autoExtendable?: boolean | undefined;
|
|
341
342
|
auctionExtensionMinutes?: number | undefined;
|
|
342
343
|
auctionShippingPaidBy?: "seller" | "winner" | undefined;
|
|
343
|
-
preOrderDeliveryDate?: string | Date | undefined;
|
|
344
344
|
preOrderDepositPercent?: number | undefined;
|
|
345
345
|
preOrderDepositAmount?: number | undefined;
|
|
346
346
|
preOrderMaxQuantity?: number | undefined;
|
|
@@ -115,9 +115,21 @@ export interface ProductListParams {
|
|
|
115
115
|
maxPrice?: number;
|
|
116
116
|
inStock?: boolean;
|
|
117
117
|
isAuction?: boolean;
|
|
118
|
+
isPreOrder?: boolean;
|
|
118
119
|
sellerId?: string;
|
|
120
|
+
storeId?: string;
|
|
119
121
|
sort?: string;
|
|
120
122
|
page?: number;
|
|
121
123
|
perPage?: number;
|
|
122
124
|
featured?: boolean;
|
|
125
|
+
/** Auction-specific: current bid range */
|
|
126
|
+
minBid?: number;
|
|
127
|
+
maxBid?: number;
|
|
128
|
+
/** Auction: end date range / Pre-order: delivery date range */
|
|
129
|
+
dateFrom?: string;
|
|
130
|
+
dateTo?: string;
|
|
131
|
+
/** Pre-order production status */
|
|
132
|
+
preOrderStatus?: string;
|
|
133
|
+
/** Shipping / free shipping */
|
|
134
|
+
freeShipping?: boolean;
|
|
123
135
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface CouponsIndexListingProps {
|
|
2
|
+
/** Pre-fetched coupons to show on first render */
|
|
3
|
+
initialCoupons?: any[];
|
|
4
|
+
/** If set, only show coupons from this store */
|
|
5
|
+
storeSlug?: string;
|
|
6
|
+
/** If set, only show coupons from this seller */
|
|
7
|
+
sellerId?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function CouponsIndexListing({ initialCoupons, storeSlug, sellerId, }: CouponsIndexListingProps): import("react/jsx-runtime").JSX.Element;
|