@mohasinac/appkit 4.11.2 → 4.12.0
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/_internal/server/features/checkout/actions.d.ts +40 -3
- package/dist/_internal/server/features/checkout/actions.js +232 -47
- package/dist/_internal/server/features/checkout/data.d.ts +10 -0
- package/dist/_internal/server/features/orders/adapters.js +20 -0
- package/dist/_internal/server/features/products/data.d.ts +12 -3
- package/dist/_internal/server/features/products/data.js +55 -4
- package/dist/_internal/server/features/products/index.d.ts +1 -1
- package/dist/_internal/server/features/products/index.js +1 -1
- package/dist/_internal/server/features/products/list-public.d.ts +47 -0
- package/dist/_internal/server/features/products/list-public.js +131 -6
- package/dist/_internal/server/features/reviews/data.d.ts +1 -1
- package/dist/_internal/server/features/reviews/data.js +3 -2
- package/dist/_internal/server/features/tester/visibility.js +3 -3
- package/dist/_internal/server/jobs/core/auctionSettlement.js +5 -2
- package/dist/_internal/server/jobs/core/testerSandboxCleanup.d.ts +1 -1
- package/dist/_internal/server/jobs/core/testerSandboxCleanup.js +4 -1
- package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +2 -0
- package/dist/_internal/server/jobs/handlers/_helpers.js +4 -1
- package/dist/_internal/shared/checkout/lanes.d.ts +20 -0
- package/dist/_internal/shared/checkout/lanes.js +28 -0
- package/dist/_internal/shared/features/checkout/config.d.ts +4 -0
- package/dist/_internal/shared/features/checkout/config.js +8 -0
- package/dist/_internal/shared/features/promotions/schema.d.ts +0 -16
- package/dist/_internal/shared/features/promotions/schema.js +0 -2
- package/dist/_internal/shared/features/reviews/config.d.ts +8 -1
- package/dist/_internal/shared/features/reviews/config.js +8 -1
- package/dist/_internal/shared/fees/calculator.d.ts +34 -2
- package/dist/_internal/shared/fees/calculator.js +45 -1
- package/dist/_internal/shared/listing-types/_registry.d.ts +88 -1
- package/dist/_internal/shared/listing-types/_registry.js +63 -1
- package/dist/_internal/shared/listing-types/art/config.d.ts +59 -0
- package/dist/_internal/shared/listing-types/art/config.js +12 -0
- package/dist/_internal/shared/listing-types/auction/config.d.ts +53 -0
- package/dist/_internal/shared/listing-types/auction/config.js +17 -0
- package/dist/_internal/shared/listing-types/classified/config.d.ts +59 -0
- package/dist/_internal/shared/listing-types/classified/config.js +14 -0
- package/dist/_internal/shared/listing-types/digital-code/config.d.ts +59 -0
- package/dist/_internal/shared/listing-types/digital-code/config.js +12 -0
- package/dist/_internal/shared/listing-types/live/config.d.ts +59 -0
- package/dist/_internal/shared/listing-types/live/config.js +14 -0
- package/dist/_internal/shared/listing-types/pre-order/config.d.ts +110 -0
- package/dist/_internal/shared/listing-types/pre-order/config.js +14 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +44 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.js +10 -0
- package/dist/_internal/shared/listing-types/standard/config.d.ts +59 -0
- package/dist/_internal/shared/listing-types/standard/config.js +9 -0
- package/dist/_internal/shared/listing-types/stickers/config.d.ts +59 -0
- package/dist/_internal/shared/listing-types/stickers/config.js +12 -0
- package/dist/client.d.ts +12 -2
- package/dist/client.js +8 -2
- package/dist/constants/api-endpoints.d.ts +6 -0
- package/dist/constants/api-endpoints.js +2 -0
- package/dist/constants/field-names.d.ts +12 -0
- package/dist/constants/field-names.js +12 -0
- package/dist/constants/table-keys.d.ts +2 -0
- package/dist/constants/table-keys.js +2 -0
- package/dist/features/account/components/UserOrdersView.js +27 -5
- package/dist/features/admin/components/AdminAddressesView.js +21 -11
- package/dist/features/admin/components/AdminArtView.d.ts +9 -0
- package/dist/features/admin/components/AdminArtView.js +11 -35
- package/dist/features/admin/components/AdminClassifiedView.d.ts +9 -0
- package/dist/features/admin/components/AdminClassifiedView.js +11 -35
- package/dist/features/admin/components/AdminCouponEditorView.js +1 -4
- package/dist/features/admin/components/AdminDigitalCodesView.d.ts +9 -0
- package/dist/features/admin/components/AdminDigitalCodesView.js +11 -42
- package/dist/features/admin/components/AdminLiveView.d.ts +9 -0
- package/dist/features/admin/components/AdminLiveView.js +11 -41
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +8 -1
- package/dist/features/admin/components/AdminOrderEditorView.js +4 -3
- package/dist/features/admin/components/AdminOrdersView.js +14 -1
- package/dist/features/admin/components/AdminPaymentMethodsView.js +18 -11
- package/dist/features/admin/components/AdminProductsView.js +34 -18
- package/dist/features/admin/components/AdminSiteConfigGuideView.js +1 -1
- package/dist/features/admin/components/AdminSiteSettingsView.js +4 -2
- package/dist/features/admin/components/AdminStickersView.d.ts +9 -0
- package/dist/features/admin/components/AdminStickersView.js +11 -35
- package/dist/features/admin/constants/filter-tabs.d.ts +26 -9
- package/dist/features/admin/constants/filter-tabs.js +22 -9
- package/dist/features/admin/hooks/useAdminListing.js +7 -2
- package/dist/features/admin/schemas/firestore.d.ts +5 -0
- package/dist/features/admin/schemas/firestore.js +1 -0
- package/dist/features/auctions/actions/bid-actions.js +2 -2
- package/dist/features/auctions/components/AuctionBidsTable.js +1 -1
- package/dist/features/auctions/components/AuctionBottomActions.js +1 -0
- package/dist/features/auctions/components/AuctionDetailPageView.js +13 -11
- package/dist/features/cart/components/CartDrawer.d.ts +9 -1
- package/dist/features/cart/components/CartDrawer.js +4 -3
- package/dist/features/cart/components/CartPriceBreakdown.d.ts +61 -0
- package/dist/features/cart/components/CartPriceBreakdown.js +25 -0
- package/dist/features/cart/components/StoreAddonsPicker.d.ts +47 -0
- package/dist/features/cart/components/StoreAddonsPicker.js +21 -0
- package/dist/features/cart/components/index.d.ts +4 -0
- package/dist/features/cart/components/index.js +2 -0
- package/dist/features/cart/repository/cart.repository.d.ts +20 -2
- package/dist/features/cart/repository/cart.repository.js +49 -0
- package/dist/features/cart/schemas/firestore.d.ts +26 -2
- package/dist/features/categories/components/BrandDetailTabs.js +31 -11
- package/dist/features/categories/components/CategoryDetailTabs.js +27 -7
- package/dist/features/categories/components/CategoryProductsListing.d.ts +9 -1
- package/dist/features/categories/components/CategoryProductsListing.js +5 -2
- package/dist/features/classified/components/ClassifiedDetailPageView.js +2 -1
- package/dist/features/contact/email.js +5 -1
- package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +17 -16
- package/dist/features/homepage/components/FeaturedResultsSection.js +1 -1
- package/dist/features/homepage/components/SectionCarousel.js +1 -1
- package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
- package/dist/features/layout/AppLayoutShell.js +9 -2
- package/dist/features/layout/BottomActions.js +62 -6
- package/dist/features/layout/BottomActionsContext.d.ts +26 -0
- package/dist/features/layout/BottomActionsContext.js +18 -1
- package/dist/features/layout/hooks/useBottomActions.d.ts +12 -1
- package/dist/features/layout/hooks/useBottomActions.js +14 -1
- package/dist/features/layout/index.d.ts +1 -1
- package/dist/features/live/components/LiveItemDetailPageView.js +7 -6
- package/dist/features/orders/components/OrderAddonBadges.d.ts +36 -0
- package/dist/features/orders/components/OrderAddonBadges.js +28 -0
- package/dist/features/orders/components/index.d.ts +1 -0
- package/dist/features/orders/components/index.js +1 -0
- package/dist/features/orders/repository/orders.repository.d.ts +36 -0
- package/dist/features/orders/repository/orders.repository.js +18 -0
- package/dist/features/orders/types/index.d.ts +38 -0
- package/dist/features/pre-orders/components/PreOrderBottomActions.d.ts +1 -1
- package/dist/features/pre-orders/components/PreOrderBottomActions.js +3 -19
- package/dist/features/products/components/ArtStickersListView.js +5 -4
- package/dist/features/products/components/AuctionsIndexListing.js +1 -2
- package/dist/features/products/components/ListingBottomActions.d.ts +28 -0
- package/dist/features/products/components/ListingBottomActions.js +25 -0
- package/dist/features/products/components/PrizeDrawBottomActions.d.ts +1 -1
- package/dist/features/products/components/PrizeDrawBottomActions.js +3 -21
- package/dist/features/products/components/PrizeDrawsListingView.js +18 -33
- package/dist/features/products/components/ProductDetailActions.js +4 -0
- package/dist/features/products/components/ProductDetailPageView.js +7 -7
- package/dist/features/products/components/ProductsIndexListing.js +87 -11
- package/dist/features/products/components/ProductsIndexPageView.js +14 -2
- package/dist/features/products/config/listing-type-listing-config.d.ts +31 -0
- package/dist/features/products/config/listing-type-listing-config.js +74 -0
- package/dist/features/products/constants/listing-tabs.d.ts +43 -198
- package/dist/features/products/constants/listing-tabs.js +71 -58
- package/dist/features/products/constants/sieve.d.ts +104 -1
- package/dist/features/products/constants/sieve.js +27 -7
- package/dist/features/products/hooks/useProducts.js +14 -0
- package/dist/features/products/repository/products.repository.d.ts +45 -1
- package/dist/features/products/repository/products.repository.js +26 -3
- package/dist/features/products/types/index.d.ts +15 -0
- package/dist/features/products/utils/listing-badge-variant.d.ts +20 -1
- package/dist/features/products/utils/listing-badge-variant.js +26 -1
- package/dist/features/products/utils/listing-type.d.ts +24 -0
- package/dist/features/products/utils/listing-type.js +48 -0
- package/dist/features/promotions/actions/coupon-actions.d.ts +7 -0
- package/dist/features/promotions/actions/coupon-actions.js +12 -1
- package/dist/features/promotions/actions/coupon-stacking.d.ts +24 -0
- package/dist/features/promotions/actions/coupon-stacking.js +21 -0
- package/dist/features/promotions/actions/index.d.ts +1 -0
- package/dist/features/promotions/actions/index.js +1 -0
- package/dist/features/promotions/actions/seller-coupon-actions.d.ts +0 -1
- package/dist/features/promotions/actions/seller-coupon-actions.js +0 -1
- package/dist/features/promotions/components/CouponHelpDetails.d.ts +20 -0
- package/dist/features/promotions/components/CouponHelpDetails.js +17 -0
- package/dist/features/promotions/components/CouponsIndexListing.js +2 -1
- package/dist/features/promotions/components/index.d.ts +2 -0
- package/dist/features/promotions/components/index.js +1 -0
- package/dist/features/promotions/constants/coupon-help.d.ts +27 -0
- package/dist/features/promotions/constants/coupon-help.js +42 -0
- package/dist/features/promotions/repository/coupons.repository.d.ts +10 -1
- package/dist/features/promotions/repository/coupons.repository.js +22 -8
- package/dist/features/promotions/schemas/firestore.d.ts +0 -1
- package/dist/features/promotions/schemas/firestore.js +1 -1
- package/dist/features/promotions/schemas/index.d.ts +0 -20
- package/dist/features/promotions/schemas/index.js +0 -1
- package/dist/features/reviews/actions/review-actions.js +5 -1
- package/dist/features/reviews/components/ReviewDetailPageView.js +2 -1
- package/dist/features/reviews/components/ReviewFilters.d.ts +7 -1
- package/dist/features/reviews/components/ReviewFilters.js +4 -3
- package/dist/features/reviews/components/ReviewsIndexListing.d.ts +6 -0
- package/dist/features/reviews/components/ReviewsIndexListing.js +9 -109
- package/dist/features/reviews/components/ReviewsListingPanel.d.ts +41 -0
- package/dist/features/reviews/components/ReviewsListingPanel.js +226 -0
- package/dist/features/reviews/components/index.d.ts +2 -0
- package/dist/features/reviews/components/index.js +1 -0
- package/dist/features/reviews/hooks/useReviews.d.ts +8 -0
- package/dist/features/reviews/hooks/useReviews.js +14 -6
- package/dist/features/reviews/repository/reviews.repository.d.ts +29 -1
- package/dist/features/reviews/repository/reviews.repository.js +77 -41
- package/dist/features/reviews/types/index.d.ts +8 -1
- package/dist/features/seller/components/SellerOrdersView.js +2 -1
- package/dist/features/seller/components/SellerProductsCards.js +2 -2
- package/dist/features/seller/components/SellerProductsView.js +21 -18
- package/dist/features/stores/actions/store-query-actions.js +1 -1
- package/dist/features/stores/components/StoreArtStickersPageView.d.ts +21 -0
- package/dist/features/stores/components/StoreArtStickersPageView.js +30 -0
- package/dist/features/stores/components/StoreAuctionsListing.js +5 -11
- package/dist/features/stores/components/StoreAuctionsPageView.d.ts +4 -1
- package/dist/features/stores/components/StoreAuctionsPageView.js +17 -17
- package/dist/features/stores/components/StoreClassifiedsListing.js +7 -0
- package/dist/features/stores/components/StoreClassifiedsPageView.d.ts +4 -1
- package/dist/features/stores/components/StoreClassifiedsPageView.js +16 -12
- package/dist/features/stores/components/StoreDetailLayoutView.js +4 -3
- package/dist/features/stores/components/StoreDigitalCodesListing.js +5 -0
- package/dist/features/stores/components/StoreDigitalCodesPageView.d.ts +4 -1
- package/dist/features/stores/components/StoreDigitalCodesPageView.js +16 -12
- package/dist/features/stores/components/StoreLiveItemsListing.js +7 -0
- package/dist/features/stores/components/StoreLiveItemsPageView.d.ts +4 -1
- package/dist/features/stores/components/StoreLiveItemsPageView.js +16 -12
- package/dist/features/stores/components/StorePreOrdersListing.js +6 -11
- package/dist/features/stores/components/StorePreOrdersPageView.d.ts +4 -1
- package/dist/features/stores/components/StorePreOrdersPageView.js +17 -17
- package/dist/features/stores/components/StorePrizeDrawsPageView.d.ts +4 -8
- package/dist/features/stores/components/StorePrizeDrawsPageView.js +17 -22
- package/dist/features/stores/components/StoreProductsListing.d.ts +7 -1
- package/dist/features/stores/components/StoreProductsListing.js +3 -2
- package/dist/features/stores/components/StoreProductsPageView.d.ts +1 -1
- package/dist/features/stores/components/StoreProductsPageView.js +16 -21
- package/dist/features/stores/components/StoreReviewsListing.d.ts +4 -0
- package/dist/features/stores/components/StoreReviewsListing.js +7 -108
- package/dist/features/stores/schemas/index.d.ts +2 -2
- package/dist/features/tester/seed-data/coupons-tester-seed-data.js +3 -3
- package/dist/features/tester/seed-data/index.d.ts +1 -0
- package/dist/features/tester/seed-data/index.js +1 -0
- package/dist/features/tester/seed-data/offers-tester-seed-data.d.ts +2 -0
- package/dist/features/tester/seed-data/offers-tester-seed-data.js +61 -0
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +939 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.js +11 -2
- package/dist/next/routing/route-map.d.ts +2 -0
- package/dist/next/routing/route-map.js +5 -0
- package/dist/react/hooks/useListingTypeFlags.d.ts +12 -19
- package/dist/react/hooks/useListingTypeFlags.js +4 -9
- package/dist/schemas/registry.d.ts +0 -12
- package/dist/seed/claimed-coupons-seed-data.js +3 -3
- package/dist/seed/coupons-seed-data.js +29 -10
- package/dist/seed/faq-seed-data.js +2 -2
- package/dist/seed/manifest.js +2 -2
- package/dist/seed/reviews-seed-data.js +52 -0
- package/dist/server-entry.d.ts +1 -0
- package/dist/server-entry.js +1 -0
- package/dist/styles.css +55 -39
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Checkbox.d.ts +10 -1
- package/dist/ui/components/Checkbox.js +14 -2
- package/dist/ui/components/Checkbox.style.css +11 -0
- package/dist/ui/components/FilterChipGroup.d.ts +21 -3
- package/dist/ui/components/FilterChipGroup.js +33 -4
- package/dist/ui/components/HorizontalScroller.d.ts +1 -2
- package/dist/ui/components/HorizontalScroller.js +9 -5
- package/dist/ui/components/HorizontalScroller.style.css +43 -38
- package/package.json +1 -1
|
@@ -9,8 +9,6 @@ const __P = {
|
|
|
9
9
|
};
|
|
10
10
|
const CLS_BREADCRUMB_LINK = "hover:text-primary-600 transition-colors";
|
|
11
11
|
const CLS_LIVE_BADGE = "inline-block rounded-full bg-warning-surface dark:bg-warning-surface px-[var(--appkit-space-2-5)] py-[var(--appkit-space-0-5)] text-warning dark:text-warning";
|
|
12
|
-
const CLS_STAR_ON = "text-warning";
|
|
13
|
-
const CLS_STAR_OFF = "text-zinc-200 dark:text-zinc-700";
|
|
14
12
|
import { ROUTES } from "../../../next";
|
|
15
13
|
import { getDefaultCurrency } from "../../../core/baseline-resolver";
|
|
16
14
|
import { formatCurrency } from "../../../utils/number.formatter";
|
|
@@ -30,7 +28,7 @@ import { FeatureBadgeList } from "../../products/components/FeatureBadge";
|
|
|
30
28
|
import { HistoryTracker } from "../../history/components/HistoryTracker";
|
|
31
29
|
import { ShareButton } from "../../products/components/ShareButton";
|
|
32
30
|
import { MarketplaceAuctionGrid } from "./MarketplaceAuctionGrid";
|
|
33
|
-
import {
|
|
31
|
+
import { ReviewsListingPanel } from "../../reviews/components/ReviewsListingPanel";
|
|
34
32
|
import { PlaceBidModalButton } from "./PlaceBidFormClient";
|
|
35
33
|
import { CollapsibleBidHistory } from "./CollapsibleBidHistory";
|
|
36
34
|
import { LiveBidPrice } from "./LiveBidPrice";
|
|
@@ -50,11 +48,18 @@ function renderAuctionInfoPanel(props) {
|
|
|
50
48
|
const { productId, title, currentBid, currency, bidCount, isEnded, endDate, buyNowPrice, featured, freeShipping, condition, category, categoryName, brand, brandSlug, productFeatures, features, descriptionHtml, safeSeller, storeHref } = props;
|
|
51
49
|
return (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Span, { size: "xs", weight: "semibold", className: CLS_LIVE_BADGE, children: "\uD83C\uDFF7\uFE0F Live Auction" }), isEnded ? (_jsx(Span, { color: "error", surface: "danger-surface", size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", children: "Ended" })) : (_jsx(Span, { color: "success", surface: "success-surface", size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", children: "Active" }))] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title })] }), _jsxs(Div, { children: [_jsx(Text, { className: "mb-0.5", color: "muted", size: "xs", children: "Current bid" }), _jsx(LiveBidPrice, { productId: productId, currentBid: currentBid, bidCount: bidCount, currency: currency, isEnded: isEnded, priceSize: "2xl", badgeSize: "sm" }), endDate && !isEnded && (_jsxs(Text, { className: "mt-1.5", color: "muted", size: "sm", children: ["Ends in ", _jsx(Span, { weight: "medium", color: "muted", children: _jsx(CountdownDisplay, { targetDate: endDate, format: "auto", expiredLabel: "Ended" }) })] })), endDate && _jsxs(Text, { className: "mt-0.5", color: "faint", size: "xs", children: [isEnded ? "Ended" : "Ends", " ", endDate.toLocaleString()] })] }), buyNowPrice !== null && !isEnded && (_jsxs(Row, { align: "center", gap: "sm", className: "border border-[var(--appkit-color-primary-200)] dark:border-[var(--appkit-color-primary-800)] bg-primary-50 dark:bg-primary-900/20", padding: "inlineSm", rounded: "lg", children: [_jsx(Span, { size: "xs", color: "muted", children: "Buy Now:" }), _jsx(Span, { size: "base", weight: "bold", className: "text-primary-700 dark:text-primary-300", children: formatCurrency(buyNowPrice, currency) })] })), _jsx(ProductFeatureBadges, { featured: featured, freeShipping: freeShipping, condition: condition ?? undefined, labels: { featured: "Featured", fasterDelivery: "Faster Delivery", ratedSeller: "Rated Seller", condition: "Condition", conditionNew: "New", conditionUsed: "Used", conditionBroken: "For Parts", conditionRefurbished: "Refurbished", returnable: "Returnable", freeShipping: "Free Shipping", codAvailable: "Cash on Delivery", emiAvailable: "EMI Available", wishlistCount: (n) => `${n} wishlisted`, categoryProductCount: (n, cat) => `${n} in ${cat}` } }), (categoryName || category || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [category && _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: categoryName || category }), !category && categoryName && _jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: categoryName }), brand && brandSlug && _jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: brand }), brand && !brandSlug && _jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: brand })] })), productFeatures && features.length > 0 && _jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures }), !productFeatures && features.length > 0 && (_jsxs(Div, { border: "subtle", surface: "muted", padding: "inline", rounded: "xl", children: [_jsx(Text, { className: "mb-2 tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "About this item" }), _jsx(Ul, { spacing: "comfortable", size: "sm", color: "primary", children: features.map((f, i) => _jsxs(Li, { layout: "flex-start", gap: "2", 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-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" }), safeSeller && (_jsx(Div, { className: `${__P.p3}`, border: "subtle", rounded: "xl", surface: "muted", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Listed by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && _jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" })] }) }))] }));
|
|
52
50
|
}
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Store reviews for the auction's seller.
|
|
53
|
+
*
|
|
54
|
+
* Was a bespoke `slice(0, 10)` block over `listReviewsBySeller()` — which concatenates
|
|
55
|
+
* per-product batches, so the "latest 10" it showed were in product order, not date
|
|
56
|
+
* order, and reviews 11+ were unreachable. `ReviewsListingPanel` pages the real
|
|
57
|
+
* store-reviews endpoint instead.
|
|
58
|
+
*/
|
|
59
|
+
function renderAuctionStoreReviews(storeSlug) {
|
|
60
|
+
if (!storeSlug)
|
|
55
61
|
return null;
|
|
56
|
-
|
|
57
|
-
return (_jsxs(Section, { className: "mt-10", children: [_jsx(Heading, { level: 2, className: "mb-2", color: "primary", size: "xl", weight: "semibold", children: "Store Reviews" }), _jsxs(Row, { className: "mb-4", align: "center", gap: "3", children: [_jsx(Span, { weight: "bold", size: "3xl", color: "primary", children: avg.toFixed(1) }), _jsxs(Div, { children: [_jsx(Row, { gap: "xs", children: [1, 2, 3, 4, 5].map((star) => _jsx(Span, { className: star <= Math.round(avg) ? CLS_STAR_ON : CLS_STAR_OFF, children: "\u2605" }, star)) }), _jsxs(Text, { size: "xs", color: "muted", children: [storeReviews.length, " review", storeReviews.length !== 1 ? "s" : ""] })] })] }), _jsx(Stack, { gap: "sm", children: storeReviews.slice(0, 10).map((review) => (_jsxs(Stack, { gap: "xs", surface: "card", padding: "sm", className: "", children: [_jsx(Row, { justify: "between", align: "center", children: _jsxs(Row, { gap: "xs", align: "center", children: [_jsx(Span, { size: "sm", weight: "medium", color: "primary", children: review.userName }), _jsx(Row, { gap: "xs", children: [1, 2, 3, 4, 5].map((star) => _jsx(Span, { size: "xs", className: star <= review.rating ? CLS_STAR_ON : CLS_STAR_OFF, children: "\u2605" }, star)) })] }) }), review.title && _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: review.title }), _jsx(Text, { className: "leading-relaxed", color: "muted", size: "sm", children: review.comment }), _jsx(Text, { size: "xs", color: "faint", children: review.productTitle })] }, review.id))) })] }));
|
|
62
|
+
return (_jsxs(Section, { className: "mt-10", children: [_jsx(Heading, { level: 2, className: "mb-2", color: "primary", size: "xl", weight: "semibold", children: "Store Reviews" }), _jsx(ReviewsListingPanel, { source: { kind: "store", storeSlug }, stateMode: "local", context: "store", emptyLabel: "This store has no reviews yet." })] }));
|
|
58
63
|
}
|
|
59
64
|
export async function AuctionDetailPageView({ id, initialAuction, onPlaceBid, onBuyNow, productFeatures }) {
|
|
60
65
|
const product = initialAuction !== undefined
|
|
@@ -64,9 +69,6 @@ export async function AuctionDetailPageView({ id, initialAuction, onPlaceBid, on
|
|
|
64
69
|
? await listBidsByProduct(String(product.id), { pageSize: 5 }).catch(() => null)
|
|
65
70
|
: null;
|
|
66
71
|
const storeId = product?.storeId;
|
|
67
|
-
const storeReviews = storeId
|
|
68
|
-
? await listReviewsBySeller(storeId).catch(() => [])
|
|
69
|
-
: [];
|
|
70
72
|
if (!product) {
|
|
71
73
|
return (_jsx(Main, { children: _jsx(Section, { padding: "y-5xl", children: _jsx(Container, { size: "md", children: _jsxs(Stack, { align: "start", gap: "md", className: "text-left", children: [_jsx(Heading, { level: 1, size: "2xl", weight: "semibold", color: "primary", children: "Auction Not Found" }), _jsx(Text, { color: "muted", children: "This auction may have ended or the link is incorrect." }), _jsx(Link, { href: String(ROUTES.PUBLIC.AUCTIONS), className: "text-[length:var(--appkit-text-sm)] font-medium text-primary-600 hover:underline", children: "Browse Auctions" })] }) }) }) }));
|
|
72
74
|
}
|
|
@@ -191,5 +193,5 @@ export async function AuctionDetailPageView({ id, initialAuction, onPlaceBid, on
|
|
|
191
193
|
slug: typeof r.slug === "string" ? r.slug : undefined,
|
|
192
194
|
}));
|
|
193
195
|
return (_jsxs(Stack, { gap: "xl", children: [related.length > 0 && (_jsx(RelatedProducts, { labels: { title: "Similar Auctions" }, renderGrid: () => (_jsx(MarketplaceAuctionGrid, { auctions: related, gridClassName: "grid grid-cols-2 gap-[var(--appkit-space-4)] sm:grid-cols-3 lg:grid-cols-4" })) })), _jsx(GroupedListingsCarousel, { groups: groups }), _jsx(RelatedItemsSection, { relatedItems: relatedItems, relatedByBrand: relatedByBrand, relatedByTags: relatedByTags, relatedByStore: relatedByStore, categoryLabel: categoryName || category || undefined, brandLabel: brand || undefined, storeLabel: storeName || undefined })] }));
|
|
194
|
-
} }), renderAuctionStoreReviews(
|
|
196
|
+
} }), renderAuctionStoreReviews(storeId), _jsx(AuctionBottomActions, { currentBid: currentBid, currency: currency, bidCount: bidCount, isEnded: isEnded, auctionEndDate: endDate })] })] }));
|
|
195
197
|
}
|
|
@@ -7,8 +7,16 @@ interface CartItemRowProps {
|
|
|
7
7
|
href?: string;
|
|
8
8
|
/** When true: grays out item, shows "Out of Stock" badge, locks qty stepper */
|
|
9
9
|
isOutOfStock?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* `"card"` (default) — self-contained card with its own surface + padding,
|
|
12
|
+
* for the cart drawer where each line stands alone.
|
|
13
|
+
* `"row"` — bare row for the cart page, where the seller group is already the
|
|
14
|
+
* card. Nesting a card inside a card there produced the card-in-a-card look
|
|
15
|
+
* and gave the overflow nowhere to be clipped.
|
|
16
|
+
*/
|
|
17
|
+
variant?: "card" | "row";
|
|
10
18
|
}
|
|
11
|
-
export declare function CartItemRow({ item, onQtyChange, onRemove, href, isOutOfStock }: CartItemRowProps): import("react").JSX.Element;
|
|
19
|
+
export declare function CartItemRow({ item, onQtyChange, onRemove, href, isOutOfStock, variant }: CartItemRowProps): import("react").JSX.Element;
|
|
12
20
|
interface CartDrawerProps {
|
|
13
21
|
isOpen: boolean;
|
|
14
22
|
onClose: () => void;
|
|
@@ -13,11 +13,12 @@ const __O = {
|
|
|
13
13
|
yAuto: "overflow-y-auto",
|
|
14
14
|
};
|
|
15
15
|
const CLS_REMOVE_BTN = "self-start text-[var(--appkit-color-text-faint)] transition hover:text-error";
|
|
16
|
-
export function CartItemRow({ item, onQtyChange, onRemove, href, isOutOfStock = false }) {
|
|
17
|
-
|
|
16
|
+
export function CartItemRow({ item, onQtyChange, onRemove, href, isOutOfStock = false, variant = "card" }) {
|
|
17
|
+
const isRow = variant === "row";
|
|
18
|
+
return (_jsxs(Div, { layout: "flex", gap: isRow ? "3" : "4", surface: isRow ? "none" : "card", padding: isRow ? "none" : "sm", className: `min-w-0 transition-opacity ${isOutOfStock ? "opacity-60" : ""}`, children: [_jsx(Div, { surface: "muted", className: `relative ${isRow ? "h-16 w-16" : "h-20 w-20"} flex-shrink-0 ${__O.hidden}`, rounded: "lg", children: item.meta.image && (_jsx(MediaImage, { src: item.meta.image, alt: item.meta.title, size: "thumbnail" })) }), _jsxs(Stack, { justify: "between", className: "flex-1 min-w-0", children: [_jsxs(Row, { gap: "xs", className: "min-w-0", align: "start", children: [href ? (_jsx(TextLink, { href: href, target: "_blank", rel: "noopener noreferrer", weight: "medium", className: "text-[var(--appkit-color-text)] hover:underline underline-offset-2 line-clamp-2", children: item.meta.title })) : (_jsx(Text, { truncate: 2, weight: "medium", color: "primary", children: item.meta.title })), isOutOfStock && (_jsx(Span, { padding: "pill-2xs", color: "error", surface: "danger-surface", weight: "semibold", className: "flex-shrink-0 text-[10px] tracking-wide", rounded: "default", transform: "uppercase", children: "Out of Stock" }))] }), item.meta.attributes &&
|
|
18
19
|
Object.keys(item.meta.attributes).length > 0 && (_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", truncate: 1, children: Object.entries(item.meta.attributes)
|
|
19
20
|
.map(([k, v]) => `${k}: ${v}`)
|
|
20
|
-
.join(", ") })), _jsxs(Row, { justify: "between", children: [_jsx(Text, { className: "text-[var(--appkit-color-text)]", weight: "semibold", children: formatCurrency(item.meta.price * item.quantity, item.meta.currency) }), onQtyChange && !isOutOfStock && (_jsxs(Row, { gap: "sm", children: [_jsx(Button, { rounded: "full", onClick: () => onQtyChange(item.id, item.quantity - 1), disabled: item.quantity <= 1, variant: "outline", size: "sm", className: "h-7 w-7 justify-center border border-neutral-200 border-[var(--appkit-color-border)] text-[length:var(--appkit-text-sm)] disabled:opacity-40", children: "\u2212" }), _jsx(Span, { size: "sm", className: "min-w-[1.5rem]", align: "center", children: item.quantity }), _jsx(Button, { rounded: "full", onClick: () => onQtyChange(item.id, item.quantity + 1), variant: "outline", size: "sm", className: "h-7 w-7 justify-center border border-neutral-200 border-[var(--appkit-color-border)] text-[length:var(--appkit-text-sm)]", children: "+" })] })), isOutOfStock && (_jsxs(Span, { size: "xs", color: "muted", children: ["Qty: ", item.quantity] }))] })] }), onRemove && (_jsx(Button, { onClick: () => onRemove(item.id), variant: "ghost", size: "sm", "aria-label": ACTIONS.CART["remove-item"].ariaLabel, className: CLS_REMOVE_BTN, children: "\u2715" }))] }));
|
|
21
|
+
.join(", ") })), _jsxs(Row, { justify: "between", className: "min-w-0", gap: "sm", children: [_jsx(Text, { className: "text-[var(--appkit-color-text)]", weight: "semibold", children: formatCurrency(item.meta.price * item.quantity, item.meta.currency) }), onQtyChange && !isOutOfStock && (_jsxs(Row, { gap: "sm", children: [_jsx(Button, { rounded: "full", onClick: () => onQtyChange(item.id, item.quantity - 1), disabled: item.quantity <= 1, variant: "outline", size: "sm", className: "h-7 w-7 justify-center border border-neutral-200 border-[var(--appkit-color-border)] text-[length:var(--appkit-text-sm)] disabled:opacity-40", children: "\u2212" }), _jsx(Span, { size: "sm", className: "min-w-[1.5rem]", align: "center", children: item.quantity }), _jsx(Button, { rounded: "full", onClick: () => onQtyChange(item.id, item.quantity + 1), variant: "outline", size: "sm", className: "h-7 w-7 justify-center border border-neutral-200 border-[var(--appkit-color-border)] text-[length:var(--appkit-text-sm)]", children: "+" })] })), isOutOfStock && (_jsxs(Span, { size: "xs", color: "muted", children: ["Qty: ", item.quantity] }))] })] }), onRemove && (_jsx(Button, { onClick: () => onRemove(item.id), variant: "ghost", size: "sm", "aria-label": ACTIONS.CART["remove-item"].ariaLabel, className: CLS_REMOVE_BTN, children: "\u2715" }))] }));
|
|
21
22
|
}
|
|
22
23
|
export function CartDrawer({ isOpen, onClose, items, subtotal = 0, currency, isLoading, onQtyChange, onRemove, onCheckout, labels = {}, }) {
|
|
23
24
|
const { hand } = useHandMode();
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* The aggregate price breakdown shown in the cart's expandable preview and in
|
|
4
|
+
* the checkout Order Summary.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately flat: one line per fee type, with a `× N` qualifier on the fees
|
|
7
|
+
* that accumulate per store. The per-store detail lives on each seller card in
|
|
8
|
+
* the cart, where the store's items and its add-on checkboxes already are —
|
|
9
|
+
* repeating it here would make the panel a second, competing place to read the
|
|
10
|
+
* same numbers.
|
|
11
|
+
*
|
|
12
|
+
* Every figure comes from the server's pricing preview, which prices only the
|
|
13
|
+
* items actually selected for checkout. A deselected item contributes to no
|
|
14
|
+
* line, and a store with nothing selected forms no order group at all, so it
|
|
15
|
+
* contributes no shipping and no add-ons.
|
|
16
|
+
*/
|
|
17
|
+
/** Mirrors `CheckoutPricingPreviewStore` without importing server code. */
|
|
18
|
+
export interface CartPriceBreakdownStore {
|
|
19
|
+
storeId: string;
|
|
20
|
+
storeName: string;
|
|
21
|
+
subtotal: number;
|
|
22
|
+
shippingFee: number;
|
|
23
|
+
codHandlingFee: number;
|
|
24
|
+
whatsappNotifyFee: number;
|
|
25
|
+
giftWrapFee: number;
|
|
26
|
+
shipmentProtectionFee: number;
|
|
27
|
+
gstAmount: number;
|
|
28
|
+
couponDiscount: number;
|
|
29
|
+
total: number;
|
|
30
|
+
}
|
|
31
|
+
/** Mirrors `CheckoutPricingPreview` without importing server code. */
|
|
32
|
+
export interface CartPriceBreakdownData {
|
|
33
|
+
subtotal: number;
|
|
34
|
+
shippingFee: number;
|
|
35
|
+
codHandlingFee: number;
|
|
36
|
+
whatsappNotifyFee: number;
|
|
37
|
+
giftWrapFee: number;
|
|
38
|
+
shipmentProtectionFee: number;
|
|
39
|
+
gstAmount: number;
|
|
40
|
+
couponDiscount: number;
|
|
41
|
+
platformFee: number;
|
|
42
|
+
gstOnFee: number;
|
|
43
|
+
total: number;
|
|
44
|
+
stores: CartPriceBreakdownStore[];
|
|
45
|
+
}
|
|
46
|
+
export interface CartPriceBreakdownProps {
|
|
47
|
+
preview: CartPriceBreakdownData | null;
|
|
48
|
+
/** Number of items included — shown in the subtotal line so a deselection is visible. */
|
|
49
|
+
itemCount?: number;
|
|
50
|
+
currency?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Shown instead of the fee lines when no server preview is available (a
|
|
53
|
+
* signed-out cart). The subtotal still renders from whatever is known.
|
|
54
|
+
*/
|
|
55
|
+
unavailableNote?: React.ReactNode;
|
|
56
|
+
/** Subtotal to show when `preview` is null (guest carts). */
|
|
57
|
+
fallbackSubtotal?: number;
|
|
58
|
+
isLoading?: boolean;
|
|
59
|
+
className?: string;
|
|
60
|
+
}
|
|
61
|
+
export declare function CartPriceBreakdown({ preview, itemCount, currency, unavailableNote, fallbackSubtotal, isLoading, className, }: CartPriceBreakdownProps): React.JSX.Element;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Div, Row, Stack, Text } from "../../../ui";
|
|
4
|
+
import { formatCurrency } from "../../../utils/number.formatter";
|
|
5
|
+
function Line({ label, amount, tone = "muted", strong = false, }) {
|
|
6
|
+
return (_jsxs(Row, { align: "center", justify: "between", gap: "sm", className: "min-w-0", children: [_jsx(Text, { size: "sm", color: tone, weight: strong ? "semibold" : undefined, className: "min-w-0", truncate: 1, children: label }), _jsx(Text, { size: "sm", color: tone, weight: strong ? "semibold" : undefined, className: "flex-shrink-0 tabular-nums", children: amount })] }));
|
|
7
|
+
}
|
|
8
|
+
/** "Shipping (2 stores)" — only qualifies when more than one store contributes. */
|
|
9
|
+
function withStoreCount(label, count) {
|
|
10
|
+
return count > 1 ? `${label} (${count} stores)` : label;
|
|
11
|
+
}
|
|
12
|
+
export function CartPriceBreakdown({ preview, itemCount, currency = "INR", unavailableNote, fallbackSubtotal = 0, isLoading = false, className = "", }) {
|
|
13
|
+
const money = (value) => formatCurrency(value, currency);
|
|
14
|
+
if (!preview) {
|
|
15
|
+
return (_jsxs(Stack, { gap: "xs", className: className, children: [_jsx(Line, { label: itemCount !== undefined ? `Subtotal (${itemCount} item${itemCount !== 1 ? "s" : ""})` : "Subtotal", amount: money(fallbackSubtotal) }), unavailableNote && (_jsx(Text, { size: "xs", color: "muted", children: unavailableNote }))] }));
|
|
16
|
+
}
|
|
17
|
+
const storeCount = preview.stores.length;
|
|
18
|
+
// Count how many stores are actually charged each per-store add-on, so the
|
|
19
|
+
// "× N" reads as the real multiplier rather than the store count.
|
|
20
|
+
const countCharged = (pick) => preview.stores.filter((s) => pick(s) > 0).length;
|
|
21
|
+
const subtotalLabel = itemCount !== undefined
|
|
22
|
+
? `Subtotal (${itemCount} item${itemCount !== 1 ? "s" : ""}${storeCount > 1 ? `, ${storeCount} stores` : ""})`
|
|
23
|
+
: "Subtotal";
|
|
24
|
+
return (_jsxs(Stack, { gap: "xs", className: className, children: [_jsx(Line, { label: subtotalLabel, amount: money(preview.subtotal) }), preview.shippingFee > 0 && (_jsx(Line, { label: withStoreCount("Shipping", storeCount), amount: money(preview.shippingFee) })), preview.codHandlingFee > 0 && (_jsx(Line, { label: "COD handling fee", amount: money(preview.codHandlingFee) })), preview.whatsappNotifyFee > 0 && (_jsx(Line, { label: withStoreCount("WhatsApp updates", countCharged((s) => s.whatsappNotifyFee)), amount: money(preview.whatsappNotifyFee) })), preview.giftWrapFee > 0 && (_jsx(Line, { label: withStoreCount("Gift wrap", countCharged((s) => s.giftWrapFee)), amount: money(preview.giftWrapFee) })), preview.shipmentProtectionFee > 0 && (_jsx(Line, { label: withStoreCount("Shipment protection", countCharged((s) => s.shipmentProtectionFee)), amount: money(preview.shipmentProtectionFee) })), preview.platformFee > 0 && _jsx(Line, { label: "Platform fee", amount: money(preview.platformFee) }), (preview.gstAmount > 0 || preview.gstOnFee > 0) && (_jsx(Line, { label: "GST", amount: money(preview.gstAmount + preview.gstOnFee) })), preview.couponDiscount > 0 && (_jsx(Line, { label: "Coupon discount", amount: `−${money(preview.couponDiscount)}`, tone: "success" })), _jsx(Div, { border: "top-subtle", paddingY: "t-sm", children: _jsx(Line, { label: "Total", amount: money(preview.total), tone: "primary", strong: true }) }), isLoading && (_jsx(Text, { size: "xs", color: "muted", children: "Updating\u2026" }))] }));
|
|
25
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Paid add-on checkboxes for ONE store.
|
|
4
|
+
*
|
|
5
|
+
* Add-on fees are billed per order group (= per store), so they are chosen per
|
|
6
|
+
* store: ticking "WhatsApp updates" for one seller must not bill it for every
|
|
7
|
+
* seller in the cart. Rendered inside that store's card in the cart, and inside
|
|
8
|
+
* that store's row of the checkout summary — Buy Now skips the cart entirely,
|
|
9
|
+
* so both surfaces have to be able to edit them.
|
|
10
|
+
*/
|
|
11
|
+
export interface StoreAddonsValue {
|
|
12
|
+
whatsappNotifyAddon?: boolean;
|
|
13
|
+
giftWrapAddon?: boolean;
|
|
14
|
+
giftWrapMessage?: string;
|
|
15
|
+
shipmentProtectionAddon?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** The subset of `siteSettings.commissions` that gates and prices the add-ons. */
|
|
18
|
+
export interface StoreAddonsRates {
|
|
19
|
+
whatsappNotifyFeeEnabled?: boolean;
|
|
20
|
+
whatsappNotifyFee?: number;
|
|
21
|
+
giftWrapFeeEnabled?: boolean;
|
|
22
|
+
giftWrapFee?: number;
|
|
23
|
+
shipmentProtectionFeeEnabled?: boolean;
|
|
24
|
+
shipmentProtectionFeePercent?: number;
|
|
25
|
+
shipmentProtectionFeeMin?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface StoreAddonsPickerProps {
|
|
28
|
+
storeId: string;
|
|
29
|
+
/** This store's subtotal — shipment protection is a percentage of it. */
|
|
30
|
+
storeSubtotal: number;
|
|
31
|
+
value: StoreAddonsValue;
|
|
32
|
+
onChange: (storeId: string, next: StoreAddonsValue) => void;
|
|
33
|
+
rates: StoreAddonsRates | null;
|
|
34
|
+
/**
|
|
35
|
+
* When true the controls render disabled with `disabledReason`. Used in the
|
|
36
|
+
* cart when none of this store's items are selected — the fees genuinely
|
|
37
|
+
* aren't being charged, and silently hiding the controls would read as the
|
|
38
|
+
* add-ons having vanished.
|
|
39
|
+
*/
|
|
40
|
+
disabled?: boolean;
|
|
41
|
+
disabledReason?: string;
|
|
42
|
+
/** Gift messages don't belong in a collapsed cart sheet — checkout only. */
|
|
43
|
+
showGiftMessage?: boolean;
|
|
44
|
+
currency?: string;
|
|
45
|
+
className?: string;
|
|
46
|
+
}
|
|
47
|
+
export declare function StoreAddonsPicker({ storeId, storeSubtotal, value, onChange, rates, disabled, disabledReason, showGiftMessage, currency, className, }: StoreAddonsPickerProps): React.JSX.Element | null;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Gift, MessageCircle, ShieldCheck } from "lucide-react";
|
|
4
|
+
import { Div, Row, Stack, Text } from "../../../ui";
|
|
5
|
+
import { IconBox } from "../../../ui/components/IconBox";
|
|
6
|
+
import { FieldCheckbox } from "../../../ui/forms/FieldCheckbox";
|
|
7
|
+
import { FieldTextarea } from "../../../ui/forms/FieldTextarea";
|
|
8
|
+
import { formatCurrency } from "../../../utils/number.formatter";
|
|
9
|
+
export function StoreAddonsPicker({ storeId, storeSubtotal, value, onChange, rates, disabled = false, disabledReason, showGiftMessage = false, currency = "INR", className = "", }) {
|
|
10
|
+
const anyEnabled = !!rates &&
|
|
11
|
+
(rates.whatsappNotifyFeeEnabled ||
|
|
12
|
+
rates.giftWrapFeeEnabled ||
|
|
13
|
+
rates.shipmentProtectionFeeEnabled);
|
|
14
|
+
if (!anyEnabled)
|
|
15
|
+
return null;
|
|
16
|
+
const money = (value) => formatCurrency(value, currency);
|
|
17
|
+
const set = (patch) => onChange(storeId, { ...value, ...patch });
|
|
18
|
+
// Mirrors computeShipmentProtectionFee so the label states the real charge.
|
|
19
|
+
const protectionFee = Math.max(rates.shipmentProtectionFeeMin ?? 30, Math.round(storeSubtotal * ((rates.shipmentProtectionFeePercent ?? 2) / 100) * 100) / 100);
|
|
20
|
+
return (_jsxs(Stack, { gap: "xs", className: `min-w-0 ${className}`, children: [disabled && disabledReason && (_jsx(Text, { size: "xs", color: "muted", children: disabledReason })), rates.whatsappNotifyFeeEnabled && (_jsxs(Row, { gap: "sm", align: "start", className: "min-w-0", children: [_jsx(IconBox, { size: "sm", tone: "brand", children: _jsx(MessageCircle, { size: 14 }) }), _jsx(Div, { className: "flex-1 min-w-0", children: _jsx(FieldCheckbox, { name: `whatsappNotifyAddon-${storeId}`, label: `WhatsApp order updates (+${money(rates.whatsappNotifyFee ?? 10)})`, checked: !!value.whatsappNotifyAddon, onChange: (checked) => set({ whatsappNotifyAddon: checked }), disabled: disabled }) })] })), rates.giftWrapFeeEnabled && (_jsxs(Row, { gap: "sm", align: "start", className: "min-w-0", children: [_jsx(IconBox, { size: "sm", tone: "brand", children: _jsx(Gift, { size: 14 }) }), _jsxs(Stack, { gap: "xs", className: "flex-1 min-w-0", children: [_jsx(FieldCheckbox, { name: `giftWrapAddon-${storeId}`, label: `Gift wrap (+${money(rates.giftWrapFee ?? 49)})`, checked: !!value.giftWrapAddon, onChange: (checked) => set({ giftWrapAddon: checked }), disabled: disabled }), showGiftMessage && value.giftWrapAddon && (_jsx(FieldTextarea, { name: `giftWrapMessage-${storeId}`, label: "Gift message (optional)", value: value.giftWrapMessage ?? "", onChange: (next) => set({ giftWrapMessage: next.slice(0, 500) }), rows: 2, maxLength: 500, showCharCount: true, placeholder: "Add a note for the recipient\u2026", disabled: disabled }))] })] })), rates.shipmentProtectionFeeEnabled && storeSubtotal > 0 && (_jsxs(Row, { gap: "sm", align: "start", className: "min-w-0", children: [_jsx(IconBox, { size: "sm", tone: "brand", children: _jsx(ShieldCheck, { size: 14 }) }), _jsx(Div, { className: "flex-1 min-w-0", children: _jsx(FieldCheckbox, { name: `shipmentProtectionAddon-${storeId}`, label: `Shipment protection (+${money(protectionFee)})`, checked: !!value.shipmentProtectionAddon, onChange: (checked) => set({ shipmentProtectionAddon: checked }), disabled: disabled }) })] }))] }));
|
|
21
|
+
}
|
|
@@ -3,6 +3,10 @@ export { CartView } from "./CartView";
|
|
|
3
3
|
export type { CartViewProps, CartOrderGroup } from "./CartView";
|
|
4
4
|
export { CartSummary } from "./CartSummary";
|
|
5
5
|
export type { CartSummaryProps } from "./CartSummary";
|
|
6
|
+
export { StoreAddonsPicker } from "./StoreAddonsPicker";
|
|
7
|
+
export type { StoreAddonsPickerProps, StoreAddonsValue, StoreAddonsRates, } from "./StoreAddonsPicker";
|
|
8
|
+
export { CartPriceBreakdown } from "./CartPriceBreakdown";
|
|
9
|
+
export type { CartPriceBreakdownProps, CartPriceBreakdownData, CartPriceBreakdownStore, } from "./CartPriceBreakdown";
|
|
6
10
|
export { CheckoutView } from "./CheckoutView";
|
|
7
11
|
export type { CheckoutViewProps } from "./CheckoutView";
|
|
8
12
|
export { CheckoutAddressStep } from "./CheckoutAddressStep";
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { CartItemRow, CartDrawer } from "./CartDrawer";
|
|
2
2
|
export { CartView } from "./CartView";
|
|
3
3
|
export { CartSummary } from "./CartSummary";
|
|
4
|
+
export { StoreAddonsPicker } from "./StoreAddonsPicker";
|
|
5
|
+
export { CartPriceBreakdown } from "./CartPriceBreakdown";
|
|
4
6
|
export { CheckoutView } from "./CheckoutView";
|
|
5
7
|
export { CheckoutAddressStep } from "./CheckoutAddressStep";
|
|
6
8
|
export { CheckoutSuccessView } from "./CheckoutSuccessView";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type DocumentReference } from "firebase-admin/firestore";
|
|
2
2
|
import type { DocumentSnapshot } from "../../../providers/db-firebase";
|
|
3
3
|
import { BaseRepository, type FirebaseSieveResult, type SieveModel } from "../../../providers/db-firebase";
|
|
4
|
-
import { type AddToCartInput, type CartAppliedCoupon, type CartDocument, type CartItemDocument, type UpdateCartItemInput } from "../schemas";
|
|
4
|
+
import { type AddToCartInput, type CartAppliedCoupon, type CartDocument, type CartStoreAddons, type CartItemDocument, type UpdateCartItemInput } from "../schemas";
|
|
5
5
|
export declare class CartRepository extends BaseRepository<CartDocument> {
|
|
6
6
|
private static readonly SIEVE_FIELDS;
|
|
7
7
|
constructor();
|
|
@@ -45,6 +45,24 @@ export declare class CartRepository extends BaseRepository<CartDocument> {
|
|
|
45
45
|
updateItemShipping(userId: string, itemId: string, providerId: string, fee: number): Promise<CartDocument>;
|
|
46
46
|
clearAllCoupons(userId: string): Promise<void>;
|
|
47
47
|
setSelectedItems(userId: string, itemIds: string[] | null): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Set (or clear) one store's add-on selections.
|
|
50
|
+
*
|
|
51
|
+
* Scoped to a single storeId so two stores' selections can't clobber each
|
|
52
|
+
* other, and written with a dotted field path so a concurrent write to a
|
|
53
|
+
* different store merges rather than overwriting the whole map.
|
|
54
|
+
*
|
|
55
|
+
* Passing an empty selection deletes the entry outright instead of leaving
|
|
56
|
+
* `{}` behind — an all-false record and an absent record mean the same thing,
|
|
57
|
+
* and keeping both shapes around invites readers that handle only one.
|
|
58
|
+
*/
|
|
59
|
+
setStoreAddons(userId: string, storeId: string, addons: CartStoreAddons | null): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Drop a store's add-on entry once its last item leaves the cart, so the map
|
|
62
|
+
* doesn't accumulate orphans. Cosmetic — an orphan is already unreachable,
|
|
63
|
+
* because a store with no items forms no order group.
|
|
64
|
+
*/
|
|
65
|
+
pruneStoreAddons(userId: string, keepStoreIds: string[]): Promise<void>;
|
|
48
66
|
/**
|
|
49
67
|
* Cloud Functions: return refs of stale carts not updated within TTL.
|
|
50
68
|
*/
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
|
+
import { FieldValue } from "firebase-admin/firestore";
|
|
2
3
|
import { DatabaseError, NotFoundError, ValidationError } from "../../../errors";
|
|
3
4
|
import { CART_FIELDS } from "../../../constants/field-names";
|
|
4
5
|
import { normalizeError } from "../../../errors/normalize";
|
|
@@ -337,6 +338,54 @@ export class CartRepository extends BaseRepository {
|
|
|
337
338
|
.doc(userId)
|
|
338
339
|
.set({ selectedItemIds: itemIds ?? null, updatedAt: new Date() }, { merge: true });
|
|
339
340
|
}
|
|
341
|
+
/**
|
|
342
|
+
* Set (or clear) one store's add-on selections.
|
|
343
|
+
*
|
|
344
|
+
* Scoped to a single storeId so two stores' selections can't clobber each
|
|
345
|
+
* other, and written with a dotted field path so a concurrent write to a
|
|
346
|
+
* different store merges rather than overwriting the whole map.
|
|
347
|
+
*
|
|
348
|
+
* Passing an empty selection deletes the entry outright instead of leaving
|
|
349
|
+
* `{}` behind — an all-false record and an absent record mean the same thing,
|
|
350
|
+
* and keeping both shapes around invites readers that handle only one.
|
|
351
|
+
*/
|
|
352
|
+
async setStoreAddons(userId, storeId, addons) {
|
|
353
|
+
const isEmpty = !addons ||
|
|
354
|
+
(!addons.whatsappNotifyAddon &&
|
|
355
|
+
!addons.giftWrapAddon &&
|
|
356
|
+
!addons.shipmentProtectionAddon);
|
|
357
|
+
await this.db
|
|
358
|
+
.collection(this.collection)
|
|
359
|
+
.doc(userId)
|
|
360
|
+
.set({
|
|
361
|
+
storeAddons: {
|
|
362
|
+
[storeId]: isEmpty ? FieldValue.delete() : addons,
|
|
363
|
+
},
|
|
364
|
+
updatedAt: new Date(),
|
|
365
|
+
}, { merge: true });
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Drop a store's add-on entry once its last item leaves the cart, so the map
|
|
369
|
+
* doesn't accumulate orphans. Cosmetic — an orphan is already unreachable,
|
|
370
|
+
* because a store with no items forms no order group.
|
|
371
|
+
*/
|
|
372
|
+
async pruneStoreAddons(userId, keepStoreIds) {
|
|
373
|
+
const cart = await this.findByUserId(userId);
|
|
374
|
+
const existing = cart?.storeAddons;
|
|
375
|
+
if (!existing)
|
|
376
|
+
return;
|
|
377
|
+
const keep = new Set(keepStoreIds);
|
|
378
|
+
const stale = Object.keys(existing).filter((storeId) => !keep.has(storeId));
|
|
379
|
+
if (stale.length === 0)
|
|
380
|
+
return;
|
|
381
|
+
await this.db
|
|
382
|
+
.collection(this.collection)
|
|
383
|
+
.doc(userId)
|
|
384
|
+
.set({
|
|
385
|
+
storeAddons: Object.fromEntries(stale.map((storeId) => [storeId, FieldValue.delete()])),
|
|
386
|
+
updatedAt: new Date(),
|
|
387
|
+
}, { merge: true });
|
|
388
|
+
}
|
|
340
389
|
/**
|
|
341
390
|
* Cloud Functions: return refs of stale carts not updated within TTL.
|
|
342
391
|
*/
|
|
@@ -82,8 +82,21 @@ export interface CartAppliedCoupon {
|
|
|
82
82
|
storeId?: string;
|
|
83
83
|
/** Item IDs (CartItemDocument.itemId) this coupon was calculated against */
|
|
84
84
|
applicableItemIds?: string[];
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Paid add-ons the buyer opted into, for ONE store.
|
|
88
|
+
*
|
|
89
|
+
* Add-on fees have always been billed per order group (= per store) by
|
|
90
|
+
* `createOrderForGroup` / `previewCheckoutPricing`, but the selection used to be
|
|
91
|
+
* a single cart-wide boolean — so one tick on "WhatsApp updates" quietly billed
|
|
92
|
+
* ₹10 × every store in the cart, with no way to opt in for just one seller.
|
|
93
|
+
* Keying the selection by storeId makes the choice match the billing.
|
|
94
|
+
*/
|
|
95
|
+
export interface CartStoreAddons {
|
|
96
|
+
whatsappNotifyAddon?: boolean;
|
|
97
|
+
giftWrapAddon?: boolean;
|
|
98
|
+
giftWrapMessage?: string;
|
|
99
|
+
shipmentProtectionAddon?: boolean;
|
|
87
100
|
}
|
|
88
101
|
export interface CartDocument extends BaseDocument {
|
|
89
102
|
userId: string;
|
|
@@ -92,6 +105,17 @@ export interface CartDocument extends BaseDocument {
|
|
|
92
105
|
appliedCoupons?: CartAppliedCoupon[];
|
|
93
106
|
/** Item IDs the user has selected for the next checkout (undefined = all items) */
|
|
94
107
|
selectedItemIds?: string[];
|
|
108
|
+
/**
|
|
109
|
+
* Per-store add-on selections, keyed by `storeId` — the same key
|
|
110
|
+
* `splitCartIntoOrderGroups` groups on.
|
|
111
|
+
*
|
|
112
|
+
* This map is the source of truth for what gets charged; the checkout request
|
|
113
|
+
* no longer carries addon booleans. A store with no selected items forms no
|
|
114
|
+
* order group, so its entry is simply never read — that is what makes
|
|
115
|
+
* "only charge a store whose items are actually checking out" structural
|
|
116
|
+
* rather than a flag someone has to remember to check.
|
|
117
|
+
*/
|
|
118
|
+
storeAddons?: Record<string, CartStoreAddons>;
|
|
95
119
|
}
|
|
96
120
|
export declare const CART_COLLECTION: "carts";
|
|
97
121
|
export declare const CART_INDEXED_FIELDS: string[];
|
|
@@ -7,14 +7,35 @@ import { AuctionsIndexListing } from "../../products/components/AuctionsIndexLis
|
|
|
7
7
|
import { PreOrdersIndexListing } from "../../pre-orders/components/PreOrdersIndexListing";
|
|
8
8
|
import { PrizeDrawsIndexListing } from "../../products/components/PrizeDrawsIndexListing";
|
|
9
9
|
import { CategoryBundlesListing } from "./CategoryBundlesListing";
|
|
10
|
-
import { CATEGORY_PAGE_TABS } from "../../products/constants/listing-tabs";
|
|
10
|
+
import { CATEGORY_PAGE_TABS, ART_STICKERS_LISTING_TYPES, } from "../../products/constants/listing-tabs";
|
|
11
|
+
import { ALL_LISTING_TYPES } from "../../../_internal/shared/listing-types/feature-flags";
|
|
12
|
+
import { pluginFor } from "../../../_internal/shared/listing-types/_registry";
|
|
13
|
+
/**
|
|
14
|
+
* Tab slug → listing/category type, derived (2026-08-21). The hand-written
|
|
15
|
+
* version covered 5 of 10 tabs, and this view's filter did `if (!mapping)
|
|
16
|
+
* return false` — so classifieds, digital codes, live items and art were
|
|
17
|
+
* silently DROPPED from every brand page rather than rendering blank (the
|
|
18
|
+
* failure mode its category-page twin had). Same root gap, opposite symptom.
|
|
19
|
+
*
|
|
20
|
+
* `stores` is intentionally absent: a brand has no store roster, which is the
|
|
21
|
+
* one real difference between this component and CategoryDetailTabs (see
|
|
22
|
+
* CLAUDE.md's Categories & Brands Reference on why the duplication stands).
|
|
23
|
+
*/
|
|
11
24
|
const TAB_TYPE_MAP = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
25
|
+
...Object.fromEntries(ALL_LISTING_TYPES.map((type) => [
|
|
26
|
+
pluginFor(type).tabSlug,
|
|
27
|
+
{ kind: "listing", type },
|
|
28
|
+
])),
|
|
16
29
|
bundles: { kind: "category", type: "bundle" },
|
|
17
30
|
};
|
|
31
|
+
/** Listing types rendered by the shared products listing rather than a dedicated one. */
|
|
32
|
+
const GENERIC_TAB_LISTING_TYPES = {
|
|
33
|
+
[pluginFor("standard").tabSlug]: ["standard"],
|
|
34
|
+
[pluginFor("classified").tabSlug]: ["classified"],
|
|
35
|
+
[pluginFor("digital-code").tabSlug]: ["digital-code"],
|
|
36
|
+
[pluginFor("live").tabSlug]: ["live"],
|
|
37
|
+
[pluginFor("art").tabSlug]: ART_STICKERS_LISTING_TYPES,
|
|
38
|
+
};
|
|
18
39
|
export function BrandDetailTabs({ brandName, initialProductsData, initialBundles = [], counts, enabledListingTypes, enabledCategoryTypes, }) {
|
|
19
40
|
const countFor = (id) => {
|
|
20
41
|
switch (id) {
|
|
@@ -28,11 +49,10 @@ export function BrandDetailTabs({ brandName, initialProductsData, initialBundles
|
|
|
28
49
|
};
|
|
29
50
|
const visibleTabs = CATEGORY_PAGE_TABS.filter((t) => {
|
|
30
51
|
const mapping = TAB_TYPE_MAP[t.id];
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
// here rather than rely on downstream JSX to silently render nothing.
|
|
52
|
+
// A tab with no mapping has no render branch below and would show a blank
|
|
53
|
+
// panel, so drop it here rather than let downstream JSX render nothing.
|
|
54
|
+
// Today that's only `stores` — a brand has no store roster. Every listing
|
|
55
|
+
// type IS mapped now (it used to also silently drop four real types).
|
|
36
56
|
if (!mapping)
|
|
37
57
|
return false;
|
|
38
58
|
if (mapping.kind === "listing" && enabledListingTypes) {
|
|
@@ -49,5 +69,5 @@ export function BrandDetailTabs({ brandName, initialProductsData, initialBundles
|
|
|
49
69
|
});
|
|
50
70
|
const firstTabId = (visibleTabs[0]?.id ?? "products");
|
|
51
71
|
const [activeTab, setActiveTab] = useState(firstTabId);
|
|
52
|
-
return (_jsxs(_Fragment, { children: [_jsx(Tabs, { value: activeTab, onChange: (v) => setActiveTab(v), className: "mb-6", children: _jsx(TabsList, { children: visibleTabs.map((t) => (_jsx(TabsTrigger, { value: t.id, badge: countFor(t.id), children: t.label }, t.id))) }) }), activeTab
|
|
72
|
+
return (_jsxs(_Fragment, { children: [_jsx(Tabs, { value: activeTab, onChange: (v) => setActiveTab(v), className: "mb-6", children: _jsx(TabsList, { children: visibleTabs.map((t) => (_jsx(TabsTrigger, { value: t.id, badge: countFor(t.id), children: t.label }, t.id))) }) }), GENERIC_TAB_LISTING_TYPES[activeTab] && (_jsx(CategoryProductsListing, { categorySlug: "", brandName: brandName, listingTypes: GENERIC_TAB_LISTING_TYPES[activeTab], initialData: activeTab === pluginFor("standard").tabSlug ? initialProductsData : undefined })), activeTab === pluginFor("auction").tabSlug && (_jsx(AuctionsIndexListing, { brandName: brandName })), activeTab === pluginFor("pre-order").tabSlug && (_jsx(PreOrdersIndexListing, { brandName: brandName })), activeTab === pluginFor("prize-draw").tabSlug && (_jsx(PrizeDrawsIndexListing, { brandName: brandName })), activeTab === "bundles" && (_jsx(CategoryBundlesListing, { initialBundles: initialBundles, brandName: brandName }))] }));
|
|
53
73
|
}
|
|
@@ -8,16 +8,36 @@ import { PreOrdersIndexListing } from "../../pre-orders/components/PreOrdersInde
|
|
|
8
8
|
import { PrizeDrawsIndexListing } from "../../products/components/PrizeDrawsIndexListing";
|
|
9
9
|
import { CategoryBundlesListing } from "./CategoryBundlesListing";
|
|
10
10
|
import { CategoryStoresListing } from "./CategoryStoresListing";
|
|
11
|
-
import { CATEGORY_PAGE_TABS } from "../../products/constants/listing-tabs";
|
|
12
|
-
|
|
11
|
+
import { CATEGORY_PAGE_TABS, ART_STICKERS_LISTING_TYPES, } from "../../products/constants/listing-tabs";
|
|
12
|
+
import { ALL_LISTING_TYPES } from "../../../_internal/shared/listing-types/feature-flags";
|
|
13
|
+
import { pluginFor } from "../../../_internal/shared/listing-types/_registry";
|
|
14
|
+
/**
|
|
15
|
+
* Maps CATEGORY_PAGE_TABS id (a `tabSlug`) → listing type / category type key
|
|
16
|
+
* for feature-flag filtering.
|
|
17
|
+
*
|
|
18
|
+
* The listing-type half is DERIVED (2026-08-21). It used to be hand-written
|
|
19
|
+
* and covered only 6 of the 10 tabs — `classifieds`, `digital-codes`, `live`
|
|
20
|
+
* and `art` had no entry, so they skipped the flag check entirely AND (worse)
|
|
21
|
+
* had no render branch below, meaning all four rendered a tab you could click
|
|
22
|
+
* that showed an empty page.
|
|
23
|
+
*/
|
|
13
24
|
const TAB_TYPE_MAP = {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
25
|
+
...Object.fromEntries(ALL_LISTING_TYPES.map((type) => [
|
|
26
|
+
pluginFor(type).tabSlug,
|
|
27
|
+
{ kind: "listing", type },
|
|
28
|
+
])),
|
|
18
29
|
bundles: { kind: "category", type: "bundle" },
|
|
19
30
|
stores: { kind: "entity", type: "stores" },
|
|
20
31
|
};
|
|
32
|
+
/** Listing types rendered by the shared products listing rather than a dedicated one. */
|
|
33
|
+
const GENERIC_TAB_LISTING_TYPES = {
|
|
34
|
+
[pluginFor("standard").tabSlug]: ["standard"],
|
|
35
|
+
[pluginFor("classified").tabSlug]: ["classified"],
|
|
36
|
+
[pluginFor("digital-code").tabSlug]: ["digital-code"],
|
|
37
|
+
[pluginFor("live").tabSlug]: ["live"],
|
|
38
|
+
// The combined Art & Stickers tab spans both types under the `art` slug.
|
|
39
|
+
[pluginFor("art").tabSlug]: ART_STICKERS_LISTING_TYPES,
|
|
40
|
+
};
|
|
21
41
|
export function CategoryDetailTabs({ categorySlug, categoryId, initialProductsData, initialBundles = [], initialStores = [], counts, enabledListingTypes, enabledCategoryTypes, }) {
|
|
22
42
|
const countFor = (id) => {
|
|
23
43
|
switch (id) {
|
|
@@ -51,5 +71,5 @@ export function CategoryDetailTabs({ categorySlug, categoryId, initialProductsDa
|
|
|
51
71
|
});
|
|
52
72
|
const firstTabId = (visibleTabs[0]?.id ?? "products");
|
|
53
73
|
const [activeTab, setActiveTab] = useState(firstTabId);
|
|
54
|
-
return (_jsxs(_Fragment, { children: [_jsx(Tabs, { value: activeTab, onChange: (v) => setActiveTab(v), className: "mb-6", children: _jsx(TabsList, { children: visibleTabs.map((t) => (_jsx(TabsTrigger, { value: t.id, badge: countFor(t.id), children: t.label }, t.id))) }) }), activeTab
|
|
74
|
+
return (_jsxs(_Fragment, { children: [_jsx(Tabs, { value: activeTab, onChange: (v) => setActiveTab(v), className: "mb-6", children: _jsx(TabsList, { children: visibleTabs.map((t) => (_jsx(TabsTrigger, { value: t.id, badge: countFor(t.id), children: t.label }, t.id))) }) }), GENERIC_TAB_LISTING_TYPES[activeTab] && (_jsx(CategoryProductsListing, { categorySlug: categorySlug, categoryId: categoryId, listingTypes: GENERIC_TAB_LISTING_TYPES[activeTab], initialData: activeTab === pluginFor("standard").tabSlug ? initialProductsData : undefined })), activeTab === pluginFor("auction").tabSlug && (_jsx(AuctionsIndexListing, { categorySlug: categorySlug })), activeTab === pluginFor("pre-order").tabSlug && (_jsx(PreOrdersIndexListing, { categorySlug: categorySlug })), activeTab === pluginFor("prize-draw").tabSlug && (_jsx(PrizeDrawsIndexListing, { categorySlug: categorySlug })), activeTab === "bundles" && (_jsx(CategoryBundlesListing, { initialBundles: initialBundles })), activeTab === "stores" && (_jsx(CategoryStoresListing, { stores: initialStores }))] }));
|
|
55
75
|
}
|
|
@@ -4,6 +4,14 @@ export interface CategoryProductsListingProps {
|
|
|
4
4
|
categoryId?: string;
|
|
5
5
|
/** Filter products by brand name (for brand detail pages) */
|
|
6
6
|
brandName?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Listing types this tab spans. Defaults to `["standard"]` — the original,
|
|
9
|
+
* only behaviour. Category/brand pages pass a single non-standard type (or
|
|
10
|
+
* the `["art","stickers"]` pair) so the classifieds / digital-codes / live /
|
|
11
|
+
* art tabs render real content instead of a blank panel: those four tabs
|
|
12
|
+
* existed in CATEGORY_PAGE_TABS but had no render branch at all.
|
|
13
|
+
*/
|
|
14
|
+
listingTypes?: readonly string[];
|
|
7
15
|
initialData?: any;
|
|
8
16
|
}
|
|
9
|
-
export declare function CategoryProductsListing({ categorySlug, categoryId, brandName, initialData, }: CategoryProductsListingProps): React.JSX.Element;
|
|
17
|
+
export declare function CategoryProductsListing({ categorySlug, categoryId, brandName, listingTypes, initialData, }: CategoryProductsListingProps): React.JSX.Element;
|
|
@@ -27,7 +27,7 @@ function renderCategoryProductGrid(props) {
|
|
|
27
27
|
}
|
|
28
28
|
return (_jsx(ProductGrid, { products: products, getProductHref: getProductHref, view: view, emptyLabel: "No products found in this category.", onWishlistToggle: onWishlistToggle, wishlistedIds: wishlistedIds, onAddToCart: onAddToCart }));
|
|
29
29
|
}
|
|
30
|
-
export function CategoryProductsListing({ categorySlug, categoryId, brandName, initialData, }) {
|
|
30
|
+
export function CategoryProductsListing({ categorySlug, categoryId, brandName, listingTypes = ["standard"], initialData, }) {
|
|
31
31
|
const table = useUrlTable({ defaults: { pageSize: "24", sort: "-createdAt" } });
|
|
32
32
|
const { showToast } = useToast();
|
|
33
33
|
const { requireAuth, modalOpen, modalMessage, closeModal } = useAuthGate();
|
|
@@ -48,7 +48,10 @@ export function CategoryProductsListing({ categorySlug, categoryId, brandName, i
|
|
|
48
48
|
sort: table.get("sort") || "-createdAt",
|
|
49
49
|
page: table.getNumber("page", 1),
|
|
50
50
|
perPage: table.getNumber("pageSize", 24),
|
|
51
|
-
|
|
51
|
+
// Pipe-joined OR-group when the tab spans several types (art + stickers);
|
|
52
|
+
// sievejs parses it as a same-field OR and the Firebase adapter upgrades
|
|
53
|
+
// it to a `.where(…, "in", …)` query.
|
|
54
|
+
listingType: listingTypes.join("|"),
|
|
52
55
|
};
|
|
53
56
|
const { products, totalPages, page, isLoading } = useProducts(params, { initialData });
|
|
54
57
|
const commitSearch = useCallback(() => {
|