@mohasinac/appkit 4.13.1 → 4.15.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/client/features/layout/DashboardLayoutClient.js +14 -5
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.d.ts +3 -0
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +6 -0
- package/dist/_internal/client/features/lottery/LotteryDetailView.d.ts +8 -0
- package/dist/_internal/client/features/lottery/LotteryDetailView.js +4 -1
- package/dist/_internal/client/features/lottery/LotteryListView.d.ts +7 -0
- package/dist/_internal/client/features/lottery/LotteryListView.js +1 -1
- package/dist/_internal/client/features/lottery/PrizeDrawLotteryDetailView.js +4 -1
- package/dist/_internal/client/features/lottery/slot-collage-items.d.ts +15 -0
- package/dist/_internal/client/features/lottery/slot-collage-items.js +13 -0
- package/dist/_internal/server/features/auctions/actions.js +8 -2
- package/dist/_internal/server/features/bundles/data.d.ts +16 -0
- package/dist/_internal/server/features/bundles/data.js +26 -0
- package/dist/_internal/server/features/bundles/index.d.ts +1 -1
- package/dist/_internal/server/features/bundles/index.js +1 -1
- package/dist/_internal/server/features/cart/service.d.ts +3 -3
- package/dist/_internal/server/features/checkout/actions.js +25 -0
- package/dist/_internal/server/features/checkout/locked-lines.d.ts +38 -0
- package/dist/_internal/server/features/checkout/locked-lines.js +171 -0
- package/dist/_internal/server/features/checkout/source-context.d.ts +5 -0
- package/dist/_internal/server/features/checkout/source-context.js +157 -0
- package/dist/_internal/server/features/homepage/index.d.ts +0 -1
- package/dist/_internal/server/features/homepage/index.js +7 -1
- package/dist/_internal/server/features/lottery/adapters.js +4 -0
- package/dist/_internal/server/features/offers/adapters.d.ts +103 -0
- package/dist/_internal/server/features/offers/adapters.js +88 -0
- package/dist/_internal/server/features/offers/index.d.ts +2 -0
- package/dist/_internal/server/features/offers/index.js +1 -0
- package/dist/_internal/server/features/orders/adapters.js +24 -0
- package/dist/_internal/server/features/products/data.js +15 -37
- 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 +46 -20
- package/dist/_internal/server/features/products/list-public.js +266 -90
- package/dist/_internal/server/features/products/listing-tab-counts.d.ts +44 -0
- package/dist/_internal/server/features/products/listing-tab-counts.js +97 -0
- package/dist/_internal/server/features/site-settings/adapters.d.ts +51 -0
- package/dist/_internal/server/features/site-settings/adapters.js +75 -0
- package/dist/_internal/server/features/site-settings/index.d.ts +1 -0
- package/dist/_internal/server/features/site-settings/index.js +1 -0
- package/dist/_internal/server/features/stores/adapters.d.ts +56 -0
- package/dist/_internal/server/features/stores/adapters.js +91 -0
- package/dist/_internal/server/features/stores/index.d.ts +1 -0
- package/dist/_internal/server/features/stores/index.js +1 -0
- package/dist/_internal/server/jobs/core/auctionSettlement.js +56 -2
- package/dist/_internal/server/jobs/core/offerExpiry.js +55 -19
- package/dist/_internal/server/jobs/core/paymentReviewAutoApprove.d.ts +5 -0
- package/dist/_internal/server/jobs/core/paymentReviewAutoApprove.js +33 -1
- package/dist/_internal/server/jobs/handlers/messages.d.ts +7 -0
- package/dist/_internal/server/jobs/handlers/messages.js +10 -0
- package/dist/_internal/shared/actions/action-registry.js +17 -1
- package/dist/_internal/shared/checkout/lanes.d.ts +25 -0
- package/dist/_internal/shared/checkout/lanes.js +26 -0
- package/dist/_internal/shared/features/auctions/config.d.ts +27 -0
- package/dist/_internal/shared/features/auctions/config.js +27 -0
- package/dist/_internal/shared/features/auctions/schema.d.ts +14 -2
- package/dist/_internal/shared/features/auctions/schema.js +13 -1
- package/dist/_internal/shared/features/blog/schema.d.ts +2 -2
- package/dist/_internal/shared/features/cart/schema.d.ts +20 -15
- package/dist/_internal/shared/features/cart/schema.js +15 -0
- package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +32 -32
- package/dist/_internal/shared/features/emi/schedule.d.ts +24 -3
- package/dist/_internal/shared/features/emi/schedule.js +20 -5
- package/dist/_internal/shared/features/events/schema.d.ts +2 -2
- package/dist/_internal/shared/features/homepage/section-gate.d.ts +14 -0
- package/dist/_internal/shared/features/homepage/section-gate.js +51 -0
- package/dist/_internal/shared/features/offers/config.d.ts +30 -0
- package/dist/_internal/shared/features/offers/config.js +59 -0
- package/dist/_internal/shared/features/orders/schema.d.ts +8 -8
- package/dist/_internal/shared/features/products/schema.d.ts +30 -30
- package/dist/_internal/shared/features/products/to-product-item.js +6 -0
- package/dist/_internal/shared/features/reviews/schema.d.ts +2 -2
- package/dist/_internal/shared/features/site-settings/fees.d.ts +60 -0
- package/dist/_internal/shared/features/site-settings/fees.js +39 -0
- package/dist/_internal/shared/features/site-settings/types.d.ts +40 -0
- package/dist/_internal/shared/features/site-settings/types.js +1 -0
- package/dist/_internal/shared/history/history.d.ts +88 -0
- package/dist/_internal/shared/history/history.js +198 -0
- package/dist/_internal/shared/history/index.d.ts +4 -0
- package/dist/_internal/shared/history/index.js +2 -0
- package/dist/_internal/shared/history/types.d.ts +61 -0
- package/dist/_internal/shared/history/types.js +6 -0
- package/dist/_internal/shared/listing-types/_registry.d.ts +54 -1
- package/dist/_internal/shared/listing-types/_registry.js +67 -0
- package/dist/_internal/shared/listing-types/art/config.d.ts +3 -0
- package/dist/_internal/shared/listing-types/art/config.js +5 -0
- package/dist/_internal/shared/listing-types/auction/config.d.ts +13 -0
- package/dist/_internal/shared/listing-types/auction/config.js +21 -0
- package/dist/_internal/shared/listing-types/availability.d.ts +79 -0
- package/dist/_internal/shared/listing-types/availability.js +104 -0
- package/dist/_internal/shared/listing-types/capabilities.d.ts +15 -0
- package/dist/_internal/shared/listing-types/capabilities.js +12 -0
- package/dist/_internal/shared/listing-types/classified/config.d.ts +3 -0
- package/dist/_internal/shared/listing-types/classified/config.js +5 -0
- package/dist/_internal/shared/listing-types/digital-code/config.d.ts +11 -0
- package/dist/_internal/shared/listing-types/digital-code/config.js +22 -2
- package/dist/_internal/shared/listing-types/live/config.d.ts +3 -0
- package/dist/_internal/shared/listing-types/live/config.js +5 -0
- package/dist/_internal/shared/listing-types/pre-order/config.d.ts +3 -0
- package/dist/_internal/shared/listing-types/pre-order/config.js +19 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +11 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.js +15 -0
- package/dist/_internal/shared/listing-types/standard/config.d.ts +3 -0
- package/dist/_internal/shared/listing-types/standard/config.js +5 -0
- package/dist/_internal/shared/listing-types/stickers/config.d.ts +3 -0
- package/dist/_internal/shared/listing-types/stickers/config.js +5 -0
- package/dist/client.d.ts +20 -7
- package/dist/client.js +22 -4
- package/dist/constants/api-endpoints.d.ts +6 -0
- package/dist/constants/api-endpoints.js +2 -0
- package/dist/constants/field-names.d.ts +57 -8
- package/dist/constants/field-names.js +101 -8
- package/dist/constants/table-keys.d.ts +17 -0
- package/dist/constants/table-keys.js +17 -0
- package/dist/errors/error-codes.d.ts +13 -1
- package/dist/errors/error-codes.js +13 -1
- package/dist/errors/error-display-map.js +12 -3
- package/dist/errors/messages.d.ts +10 -2
- package/dist/errors/messages.js +10 -2
- package/dist/features/about/components/AboutView.d.ts +3 -0
- package/dist/features/about/components/AboutView.js +3 -2
- package/dist/features/about/components/PolicyPageView.d.ts +2 -1
- package/dist/features/about/components/PolicyPageView.js +61 -23
- package/dist/features/about/components/PublicProfileView.js +5 -0
- package/dist/features/about/schemas/firestore.d.ts +3 -0
- package/dist/features/account/components/UserBidsView.js +12 -4
- package/dist/features/account/components/UserOrdersView.js +7 -0
- package/dist/features/account/components/UserReturnsView.d.ts +3 -0
- package/dist/features/account/components/UserReturnsView.js +17 -1
- package/dist/features/account/components/index.d.ts +0 -2
- package/dist/features/account/components/index.js +0 -1
- package/dist/features/account/schemas/index.d.ts +2 -2
- package/dist/features/account/types/index.d.ts +21 -2
- package/dist/features/addresses/schemas/index.d.ts +4 -4
- package/dist/features/admin/components/AdminArtView.js +8 -3
- package/dist/features/admin/components/AdminAuditLogView.js +1 -0
- package/dist/features/admin/components/AdminBidsView.js +15 -2
- package/dist/features/admin/components/AdminBlogEditorView.js +65 -33
- package/dist/features/admin/components/AdminClassifiedView.js +8 -3
- package/dist/features/admin/components/AdminDigitalCodesView.js +8 -3
- package/dist/features/admin/components/AdminFeatureEditorView.js +35 -6
- package/dist/features/admin/components/AdminFeatureFlagsView.js +1 -0
- package/dist/features/admin/components/AdminLiveView.js +8 -3
- package/dist/features/admin/components/AdminNavEditorView.js +15 -5
- package/dist/features/admin/components/AdminNewsletterView.js +18 -2
- package/dist/features/admin/components/AdminOffersView.d.ts +4 -0
- package/dist/features/admin/components/AdminOffersView.js +230 -0
- package/dist/features/admin/components/AdminOrdersView.js +10 -1
- package/dist/features/admin/components/AdminPaymentMethodsView.js +24 -4
- package/dist/features/admin/components/AdminPrizeDrawsView.js +5 -0
- package/dist/features/admin/components/AdminProductsView.js +16 -29
- package/dist/features/admin/components/AdminScammerEditorView.d.ts +2 -1
- package/dist/features/admin/components/AdminScammerEditorView.js +3 -13
- package/dist/features/admin/components/AdminScammersView.js +3 -8
- package/dist/features/admin/components/AdminSectionsView.js +32 -23
- package/dist/features/admin/components/AdminSessionsView.js +17 -2
- package/dist/features/admin/components/AdminSiteSettingsView.js +31 -13
- package/dist/features/admin/components/AdminSiteView.js +6 -2
- package/dist/features/admin/components/AdminStickersView.js +8 -3
- package/dist/features/admin/components/ViewAuditLogEntryModal.js +1 -0
- package/dist/features/admin/components/index.d.ts +2 -0
- package/dist/features/admin/components/index.js +1 -0
- package/dist/features/admin/components/sections/adminSectionsTypes.d.ts +4 -2
- package/dist/features/admin/components/sections/adminSectionsTypes.js +45 -27
- package/dist/features/admin/constants/filter-tabs.d.ts +89 -0
- package/dist/features/admin/constants/filter-tabs.js +56 -0
- package/dist/features/admin/schemas/firestore.d.ts +122 -3
- package/dist/features/admin/schemas/firestore.js +1 -0
- package/dist/features/admin/schemas/homepage-section-form.d.ts +22 -0
- package/dist/features/admin/schemas/homepage-section-form.js +68 -0
- package/dist/features/admin/schemas/nav-item-form.d.ts +74 -0
- package/dist/features/admin/schemas/nav-item-form.js +72 -0
- package/dist/features/auctions/actions/bid-actions.d.ts +20 -5
- package/dist/features/auctions/actions/bid-actions.js +93 -20
- package/dist/features/auctions/components/AuctionBidsTable.d.ts +10 -1
- package/dist/features/auctions/components/AuctionBidsTable.js +22 -4
- package/dist/features/auctions/components/AuctionDetailPageView.js +11 -6
- package/dist/features/auctions/components/AuctionsListView.js +5 -6
- package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +7 -0
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +19 -2
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +1 -2
- package/dist/features/auctions/components/PlaceBidFormClient.js +8 -4
- package/dist/features/auctions/components/bid-detail-fields.d.ts +29 -0
- package/dist/features/auctions/components/bid-detail-fields.js +103 -0
- package/dist/features/auctions/repository/bid.repository.d.ts +13 -0
- package/dist/features/auctions/repository/bid.repository.js +23 -0
- package/dist/features/auctions/schemas/firestore.d.ts +17 -0
- package/dist/features/auctions/schemas/index.d.ts +12 -12
- package/dist/features/audit-log/schemas/firestore.d.ts +14 -0
- package/dist/features/audit-log/schemas/firestore.js +7 -0
- package/dist/features/auth/permissions/constants.d.ts +1 -1
- package/dist/features/auth/permissions/constants.js +10 -1
- package/dist/features/blog/actions/blog-actions.d.ts +7 -7
- package/dist/features/blog/schemas/blog-form.d.ts +80 -0
- package/dist/features/blog/schemas/blog-form.js +100 -0
- package/dist/features/blog/schemas/index.d.ts +6 -6
- package/dist/features/cart/repository/cart.repository.js +1 -0
- package/dist/features/cart/schemas/firestore.d.ts +15 -0
- package/dist/features/catalogue/schemas/validation.d.ts +4 -4
- package/dist/features/categories/components/BrandDetailPageView.js +23 -40
- package/dist/features/categories/components/BrandDetailTabs.d.ts +7 -7
- package/dist/features/categories/components/BrandDetailTabs.js +8 -12
- package/dist/features/categories/components/BundleCollage.js +1 -1
- package/dist/features/categories/components/CategoryDetailPageView.js +53 -57
- package/dist/features/categories/components/CategoryDetailTabs.d.ts +10 -8
- package/dist/features/categories/components/CategoryDetailTabs.js +11 -16
- package/dist/features/categories/components/CategoryProductsListing.js +11 -1
- package/dist/features/categories/components/ConcernCard.js +7 -1
- package/dist/features/categories/schemas/firestore.d.ts +13 -0
- package/dist/features/categories/schemas/index.d.ts +26 -26
- package/dist/features/classified/components/ClassifiedDetailPageView.d.ts +16 -1
- package/dist/features/classified/components/ClassifiedDetailPageView.js +23 -3
- package/dist/features/events/components/AdminEventEditorView.js +32 -54
- package/dist/features/events/components/AdminEventEntriesView.js +22 -3
- package/dist/features/events/components/AdminEventsView.js +15 -11
- package/dist/features/events/components/EventFilters.d.ts +9 -1
- package/dist/features/events/components/EventFilters.js +45 -21
- package/dist/features/events/components/EventsIndexListing.d.ts +6 -1
- package/dist/features/events/components/EventsIndexListing.js +2 -2
- package/dist/features/events/components/EventsListPageView.js +13 -4
- package/dist/features/events/repository/events.repository.d.ts +17 -0
- package/dist/features/events/repository/events.repository.js +27 -0
- package/dist/features/events/schemas/event-form.d.ts +113 -0
- package/dist/features/events/schemas/event-form.js +82 -0
- package/dist/features/events/schemas/firestore.d.ts +12 -0
- package/dist/features/events/schemas/firestore.js +12 -0
- package/dist/features/events/schemas/index.d.ts +50 -50
- package/dist/features/events/types/index.d.ts +11 -0
- package/dist/features/events/types/index.js +57 -1
- package/dist/features/faq/actions/faq-actions.d.ts +12 -12
- package/dist/features/faq/schemas/index.d.ts +8 -8
- package/dist/features/filters/FilterFacetSection.d.ts +7 -1
- package/dist/features/filters/FilterFacetSection.js +11 -3
- package/dist/features/grouped/schemas/grouped-listing-form.d.ts +101 -0
- package/dist/features/grouped/schemas/grouped-listing-form.js +89 -0
- package/dist/features/grouped/schemas/index.d.ts +4 -4
- package/dist/features/homepage/actions/homepage-section-actions.d.ts +11 -3
- package/dist/features/homepage/actions/homepage-section-actions.js +33 -1
- package/dist/features/homepage/components/AnnouncementBar.d.ts +7 -1
- package/dist/features/homepage/components/AnnouncementBar.js +3 -3
- package/dist/features/homepage/components/MarketplaceHomepageView.js +20 -6
- package/dist/features/homepage/hooks/homepage-query.d.ts +16 -0
- package/dist/features/homepage/hooks/homepage-query.js +45 -0
- package/dist/features/homepage/hooks/useFeaturedAuctions.d.ts +10 -0
- package/dist/features/homepage/hooks/useFeaturedAuctions.js +28 -8
- package/dist/features/homepage/hooks/useFeaturedPreOrders.js +17 -8
- package/dist/features/homepage/hooks/useFeaturedProducts.js +15 -8
- package/dist/features/homepage/schemas/index.d.ts +33 -2
- package/dist/features/homepage/schemas/index.js +24 -0
- package/dist/features/layout/AppLayoutShell.d.ts +11 -1
- package/dist/features/layout/AppLayoutShell.js +15 -14
- package/dist/features/layout/BackToTop.d.ts +9 -0
- package/dist/features/layout/BackToTop.js +10 -1
- package/dist/features/layout/BottomActions.js +28 -25
- package/dist/features/layout/BottomChrome.d.ts +34 -0
- package/dist/features/layout/BottomChrome.js +55 -0
- package/dist/features/layout/BottomNavLayout.d.ts +6 -0
- package/dist/features/layout/BottomNavLayout.js +7 -1
- package/dist/features/layout/BottomNavbar.js +15 -2
- package/dist/features/layout/FooterLayout.js +11 -1
- package/dist/features/layout/NavbarLayout.js +1 -1
- package/dist/features/layout/TitleBarLayout.d.ts +7 -0
- package/dist/features/layout/TitleBarLayout.js +28 -5
- package/dist/features/layout/index.d.ts +2 -0
- package/dist/features/layout/index.js +1 -0
- package/dist/features/lottery/types/index.d.ts +3 -0
- package/dist/features/media/MediaPickerModal.js +5 -1
- package/dist/features/messages/schemas/index.d.ts +2 -2
- package/dist/features/orders/components/OrderScopeTabs.d.ts +20 -0
- package/dist/features/orders/components/OrderScopeTabs.js +31 -0
- package/dist/features/orders/components/OrderStatusTimeline.d.ts +5 -1
- package/dist/features/orders/components/OrderStatusTimeline.js +39 -20
- package/dist/features/orders/constants/order-scope.d.ts +48 -0
- package/dist/features/orders/constants/order-scope.js +111 -0
- package/dist/features/orders/repository/orders.repository.d.ts +54 -7
- package/dist/features/orders/repository/orders.repository.js +121 -22
- package/dist/features/orders/schemas/firestore.d.ts +152 -0
- package/dist/features/orders/schemas/firestore.js +30 -0
- package/dist/features/orders/schemas/index.d.ts +18 -18
- package/dist/features/orders/types/index.d.ts +35 -7
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +14 -9
- package/dist/features/pre-orders/components/PreOrdersListView.js +6 -5
- package/dist/features/pre-orders/schemas/index.d.ts +1 -1
- package/dist/features/products/actions/product-actions.d.ts +14 -0
- package/dist/features/products/actions/product-actions.js +49 -11
- package/dist/features/products/api/route.d.ts +12 -0
- package/dist/features/products/api/route.js +42 -100
- package/dist/features/products/components/ArtStickersListView.js +2 -2
- package/dist/features/products/components/AuctionsIndexListing.js +13 -11
- package/dist/features/products/components/AvailabilityTabs.d.ts +11 -0
- package/dist/features/products/components/AvailabilityTabs.js +47 -0
- package/dist/features/products/components/GroupSettingsPanel.js +5 -1
- package/dist/features/products/components/MakeOfferButton.js +48 -49
- package/dist/features/products/components/PrizeDrawCollage.d.ts +20 -4
- package/dist/features/products/components/PrizeDrawCollage.js +9 -1
- package/dist/features/products/components/PrizeDrawsIndexListing.js +14 -12
- package/dist/features/products/components/PrizeDrawsListingView.js +2 -2
- package/dist/features/products/components/PrizeDrawsSection.d.ts +7 -1
- package/dist/features/products/components/PrizeDrawsSection.js +23 -18
- package/dist/features/products/components/ProductDetailPageView.d.ts +4 -1
- package/dist/features/products/components/ProductDetailPageView.js +18 -4
- package/dist/features/products/components/ProductForm.js +5 -2
- package/dist/features/products/components/ProductGrid.js +6 -1
- package/dist/features/products/components/ProductsIndexListing.js +22 -32
- package/dist/features/products/components/ProductsIndexPageView.js +6 -9
- package/dist/features/products/components/index.d.ts +2 -2
- package/dist/features/products/components/index.js +1 -1
- package/dist/features/products/config/listing-type-listing-config.d.ts +20 -0
- package/dist/features/products/config/listing-type-listing-config.js +22 -10
- package/dist/features/products/constants/action-defs.d.ts +2 -0
- package/dist/features/products/constants/action-defs.js +6 -0
- package/dist/features/products/hooks/useAvailabilityScope.d.ts +13 -0
- package/dist/features/products/hooks/useAvailabilityScope.js +19 -0
- package/dist/features/products/hooks/useProducts.d.ts +5 -0
- package/dist/features/products/hooks/useProducts.js +13 -2
- package/dist/features/products/repository/products.repository.d.ts +31 -2
- package/dist/features/products/repository/products.repository.js +53 -21
- package/dist/features/products/schemas/firestore.d.ts +17 -3
- package/dist/features/products/schemas/firestore.js +7 -2
- package/dist/features/products/schemas/index.d.ts +64 -64
- package/dist/features/products/schemas/product-features.validators.d.ts +79 -6
- package/dist/features/products/schemas/product-features.validators.js +63 -0
- package/dist/features/products/types/index.d.ts +16 -1
- package/dist/features/promotions/schemas/index.d.ts +2 -2
- package/dist/features/reviews/schemas/index.d.ts +16 -16
- package/dist/features/scams/constants/scammer-status-display.d.ts +28 -0
- package/dist/features/scams/constants/scammer-status-display.js +79 -0
- package/dist/features/search/schemas/index.d.ts +4 -4
- package/dist/features/seller/actions/offer-actions.js +57 -4
- package/dist/features/seller/components/OfferPhaseTimeline.d.ts +59 -0
- package/dist/features/seller/components/OfferPhaseTimeline.js +107 -0
- package/dist/features/seller/components/SellerArtView.js +5 -0
- package/dist/features/seller/components/SellerAuctionsView.js +13 -15
- package/dist/features/seller/components/SellerBidsView.js +11 -4
- package/dist/features/seller/components/SellerClassifiedView.js +5 -0
- package/dist/features/seller/components/SellerCouponEditorView.js +17 -17
- package/dist/features/seller/components/SellerDigitalCodesView.js +5 -0
- package/dist/features/seller/components/SellerLiveView.js +5 -0
- package/dist/features/seller/components/SellerOffersView.js +2 -1
- package/dist/features/seller/components/SellerOrdersView.js +31 -9
- package/dist/features/seller/components/SellerPayoutSettingsView.js +56 -31
- package/dist/features/seller/components/SellerPreOrdersView.js +5 -0
- package/dist/features/seller/components/SellerPrizeDrawsView.js +14 -16
- package/dist/features/seller/components/SellerProductShell.js +7 -1
- package/dist/features/seller/components/SellerProductsView.js +11 -8
- package/dist/features/seller/components/SellerStickersView.js +5 -0
- package/dist/features/seller/components/index.d.ts +0 -2
- package/dist/features/seller/components/index.js +0 -1
- package/dist/features/seller/repository/offer.repository.d.ts +142 -24
- package/dist/features/seller/repository/offer.repository.js +165 -26
- package/dist/features/seller/schemas/coupon-form.d.ts +72 -0
- package/dist/features/seller/schemas/coupon-form.js +116 -0
- package/dist/features/seller/schemas/firestore.d.ts +56 -2
- package/dist/features/seller/schemas/firestore.js +27 -0
- package/dist/features/seller/schemas/index.d.ts +2 -2
- package/dist/features/seller/schemas/offer-forms.d.ts +46 -0
- package/dist/features/seller/schemas/offer-forms.js +42 -0
- package/dist/features/seller/server.d.ts +0 -1
- package/dist/features/seller/server.js +0 -1
- package/dist/features/shell/SectionForm.d.ts +99 -0
- package/dist/features/shell/SectionForm.js +128 -0
- package/dist/features/shell/build-sections.d.ts +63 -0
- package/dist/features/shell/build-sections.js +207 -0
- package/dist/features/shell/entity-form.d.ts +109 -0
- package/dist/features/shell/entity-form.js +96 -0
- package/dist/features/shell/field-ui-meta.d.ts +115 -0
- package/dist/features/shell/field-ui-meta.js +324 -0
- package/dist/features/shell/index.d.ts +8 -0
- package/dist/features/shell/index.js +4 -0
- package/dist/features/status-history/components/StatusTimeline.d.ts +74 -0
- package/dist/features/status-history/components/StatusTimeline.js +46 -0
- package/dist/features/status-history/index.d.ts +11 -0
- package/dist/features/status-history/index.js +10 -0
- package/dist/features/store-extensions/components/ReviewDecisionModal.d.ts +27 -0
- package/dist/features/store-extensions/components/ReviewDecisionModal.js +82 -0
- package/dist/features/store-extensions/index.d.ts +12 -0
- package/dist/features/store-extensions/index.js +12 -0
- package/dist/features/store-extensions/schemas/admin-notification-form.d.ts +36 -0
- package/dist/features/store-extensions/schemas/admin-notification-form.js +114 -0
- package/dist/features/store-extensions/schemas/analytics-forms.d.ts +57 -0
- package/dist/features/store-extensions/schemas/analytics-forms.js +158 -0
- package/dist/features/store-extensions/schemas/custom-role-form.d.ts +85 -0
- package/dist/features/store-extensions/schemas/custom-role-form.js +118 -0
- package/dist/features/store-extensions/schemas/firestore.d.ts +21 -1
- package/dist/features/store-extensions/schemas/firestore.js +60 -0
- package/dist/features/store-extensions/schemas/google-config-form.d.ts +53 -0
- package/dist/features/store-extensions/schemas/google-config-form.js +105 -0
- package/dist/features/store-extensions/schemas/item-request-create-form.d.ts +28 -0
- package/dist/features/store-extensions/schemas/item-request-create-form.js +80 -0
- package/dist/features/store-extensions/schemas/listing-template-form.d.ts +78 -0
- package/dist/features/store-extensions/schemas/listing-template-form.js +87 -0
- package/dist/features/store-extensions/schemas/moderation-review-form.d.ts +49 -0
- package/dist/features/store-extensions/schemas/moderation-review-form.js +101 -0
- package/dist/features/store-extensions/schemas/payout-method-form.d.ts +153 -0
- package/dist/features/store-extensions/schemas/payout-method-form.js +155 -0
- package/dist/features/store-extensions/schemas/report-create-form.d.ts +25 -0
- package/dist/features/store-extensions/schemas/report-create-form.js +79 -0
- package/dist/features/store-extensions/schemas/report-review-form.d.ts +47 -0
- package/dist/features/store-extensions/schemas/report-review-form.js +111 -0
- package/dist/features/store-extensions/schemas/shipping-config-form.d.ts +148 -0
- package/dist/features/store-extensions/schemas/shipping-config-form.js +104 -0
- package/dist/features/store-extensions/schemas/store-category-form.d.ts +54 -0
- package/dist/features/store-extensions/schemas/store-category-form.js +73 -0
- package/dist/features/stores/api/[storeSlug]/route.js +2 -25
- package/dist/features/stores/components/StoreAuctionsListing.js +15 -1
- package/dist/features/stores/components/StoreClassifiedsListing.js +12 -2
- package/dist/features/stores/components/StoreDetailLayoutView.js +24 -71
- package/dist/features/stores/components/StoreDigitalCodesListing.js +12 -2
- package/dist/features/stores/components/StoreLiveItemsListing.js +12 -2
- package/dist/features/stores/components/StorePreOrdersListing.js +12 -1
- package/dist/features/stores/components/StoreProductsListing.js +11 -1
- package/dist/features/stores/repository/store.repository.js +6 -0
- package/dist/features/stores/schemas/index.d.ts +6 -6
- package/dist/features/support/schemas/index.d.ts +4 -4
- package/dist/features/tester/actions/checklist-item-actions.d.ts +4 -4
- package/dist/features/tester/seed-data/offers-tester-seed-data.js +119 -3
- package/dist/features/tester/seed-data/products-tester-seed-data.js +131 -1
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +431 -1
- package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +1 -1
- package/dist/index.d.ts +25 -12
- package/dist/index.js +26 -11
- package/dist/next/routing/route-map.d.ts +23 -3
- package/dist/next/routing/route-map.js +14 -2
- package/dist/providers/db-firebase/base.repository.d.ts +30 -0
- package/dist/providers/db-firebase/base.repository.js +50 -1
- package/dist/providers/db-firebase/index.d.ts +1 -1
- package/dist/providers/db-firebase/index.js +1 -1
- package/dist/providers/db-firebase/sieve.d.ts +25 -0
- package/dist/providers/db-firebase/sieve.js +52 -18
- package/dist/react/hooks/useUrlTable.js +10 -1
- package/dist/schemas/firestore-value.d.ts +8 -0
- package/dist/schemas/firestore-value.js +44 -0
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas/index.js +1 -1
- package/dist/schemas/registry.d.ts +702 -69
- package/dist/schemas/registry.js +64 -1
- package/dist/seed/_helpers/category-forest.d.ts +2 -0
- package/dist/seed/_helpers/category-forest.js +309 -0
- package/dist/seed/_helpers/category-tree.d.ts +27 -0
- package/dist/seed/_helpers/category-tree.js +103 -0
- package/dist/seed/bids-seed-data.js +30 -0
- package/dist/seed/catalogue-seed-data.js +12 -12
- package/dist/seed/categories-seed-data.js +177 -162
- package/dist/seed/events-seed-data.js +61 -10
- package/dist/seed/homepage-sections-seed-data.js +5 -1
- package/dist/seed/offers-seed-data.js +244 -34
- package/dist/seed/orders-seed-data.js +336 -1
- package/dist/seed/products-art-seed-data.js +50 -12
- package/dist/seed/products-auctions-seed-data.js +138 -18
- package/dist/seed/products-classifieds-seed-data.js +80 -16
- package/dist/seed/products-digital-codes-seed-data.js +66 -18
- package/dist/seed/products-live-items-seed-data.js +64 -13
- package/dist/seed/products-preorders-seed-data.d.ts +0 -1
- package/dist/seed/products-preorders-seed-data.js +52 -13
- package/dist/seed/products-prize-draws-seed-data.js +63 -14
- package/dist/seed/products-standard-seed-data.js +99 -26
- package/dist/seed/products-stickers-seed-data.js +47 -12
- package/dist/seed/reviews-seed-data.js +6 -1
- package/dist/seed/site-settings-seed-data.d.ts +1 -38
- package/dist/seed/site-settings-seed-data.js +38 -76
- package/dist/seed/stores-seed-data.js +71 -1
- package/dist/seed/users-seed-data.js +15 -5
- package/dist/server-entry.d.ts +1 -2
- package/dist/server-entry.js +4 -3
- package/dist/server.d.ts +6 -3
- package/dist/server.js +13 -4
- package/dist/styles.css +78 -12
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Button.style.css +19 -4
- package/dist/ui/components/CollapsibleSection.d.ts +17 -1
- package/dist/ui/components/CollapsibleSection.js +6 -2
- package/dist/ui/components/FilterChipGroup.d.ts +25 -1
- package/dist/ui/components/FilterChipGroup.js +21 -4
- package/dist/ui/components/HandMode.style.css +47 -0
- package/dist/ui/components/Layout.d.ts +9 -2
- package/dist/ui/components/Layout.js +1 -1
- package/dist/ui/components/ListingLayout.js +8 -8
- package/dist/ui/components/ListingLayout.style.css +8 -7
- package/dist/ui/components/RecordDetailModal.d.ts +10 -1
- package/dist/ui/components/RecordDetailModal.js +2 -2
- package/dist/ui/components/StickyToolbar.d.ts +3 -1
- package/dist/ui/components/StickyToolbar.js +62 -20
- package/dist/ui/components/bottom-chrome-slot.d.ts +21 -0
- package/dist/ui/components/bottom-chrome-slot.js +29 -0
- package/dist/ui/components/index.style.css +3 -0
- package/dist/ui/forms/FormShell.d.ts +43 -13
- package/dist/ui/forms/FormShell.js +22 -11
- package/dist/ui/forms/index.d.ts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/package.json +2 -1
- package/scripts/node-esm-loader.mjs +65 -0
- package/scripts/seed-cli-loader.mjs +6 -49
- package/scripts/seed-cli.mjs +1 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* 🛑 `annotate()` must be the OUTERMOST call on each field — it keys a WeakMap
|
|
4
|
+
* by schema instance and every zod wrapper returns a new one.
|
|
5
|
+
*/
|
|
6
|
+
export declare const navItemFormSchema: z.ZodObject<{
|
|
7
|
+
label: z.ZodString;
|
|
8
|
+
href: z.ZodEffects<z.ZodString, string, string>;
|
|
9
|
+
icon: z.ZodOptional<z.ZodString>;
|
|
10
|
+
order: z.ZodOptional<z.ZodNumber>;
|
|
11
|
+
parentId: z.ZodOptional<z.ZodString>;
|
|
12
|
+
isVisible: z.ZodOptional<z.ZodBoolean>;
|
|
13
|
+
}, "strip", z.ZodTypeAny, {
|
|
14
|
+
label: string;
|
|
15
|
+
href: string;
|
|
16
|
+
icon?: string | undefined;
|
|
17
|
+
order?: number | undefined;
|
|
18
|
+
parentId?: string | undefined;
|
|
19
|
+
isVisible?: boolean | undefined;
|
|
20
|
+
}, {
|
|
21
|
+
label: string;
|
|
22
|
+
href: string;
|
|
23
|
+
icon?: string | undefined;
|
|
24
|
+
order?: number | undefined;
|
|
25
|
+
parentId?: string | undefined;
|
|
26
|
+
isVisible?: boolean | undefined;
|
|
27
|
+
}>;
|
|
28
|
+
export type NavItemFormValues = z.infer<typeof navItemFormSchema>;
|
|
29
|
+
/** Create contract — `label` and `href` are what make an item usable at all. */
|
|
30
|
+
export declare const navItemCreateSchema: z.ZodObject<{
|
|
31
|
+
label: z.ZodString;
|
|
32
|
+
href: z.ZodEffects<z.ZodString, string, string>;
|
|
33
|
+
icon: z.ZodOptional<z.ZodString>;
|
|
34
|
+
order: z.ZodOptional<z.ZodNumber>;
|
|
35
|
+
parentId: z.ZodOptional<z.ZodString>;
|
|
36
|
+
isVisible: z.ZodOptional<z.ZodBoolean>;
|
|
37
|
+
}, "strip", z.ZodTypeAny, {
|
|
38
|
+
label: string;
|
|
39
|
+
href: string;
|
|
40
|
+
icon?: string | undefined;
|
|
41
|
+
order?: number | undefined;
|
|
42
|
+
parentId?: string | undefined;
|
|
43
|
+
isVisible?: boolean | undefined;
|
|
44
|
+
}, {
|
|
45
|
+
label: string;
|
|
46
|
+
href: string;
|
|
47
|
+
icon?: string | undefined;
|
|
48
|
+
order?: number | undefined;
|
|
49
|
+
parentId?: string | undefined;
|
|
50
|
+
isVisible?: boolean | undefined;
|
|
51
|
+
}>;
|
|
52
|
+
/** Update contract — every field optional. */
|
|
53
|
+
export declare const navItemUpdateSchema: z.ZodObject<{
|
|
54
|
+
label: z.ZodOptional<z.ZodString>;
|
|
55
|
+
href: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
56
|
+
icon: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
57
|
+
order: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
|
|
58
|
+
parentId: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
59
|
+
isVisible: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
|
|
60
|
+
}, "strip", z.ZodTypeAny, {
|
|
61
|
+
label?: string | undefined;
|
|
62
|
+
icon?: string | undefined;
|
|
63
|
+
href?: string | undefined;
|
|
64
|
+
order?: number | undefined;
|
|
65
|
+
parentId?: string | undefined;
|
|
66
|
+
isVisible?: boolean | undefined;
|
|
67
|
+
}, {
|
|
68
|
+
label?: string | undefined;
|
|
69
|
+
icon?: string | undefined;
|
|
70
|
+
href?: string | undefined;
|
|
71
|
+
order?: number | undefined;
|
|
72
|
+
parentId?: string | undefined;
|
|
73
|
+
isVisible?: boolean | undefined;
|
|
74
|
+
}>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WHY: `AdminNavEditorView` rendered a `<Form>` with no `schema` prop, so the
|
|
3
|
+
* only validation on a navigation item was whatever the server said after
|
|
4
|
+
* the round-trip — a 400 banner rather than an error on the field that
|
|
5
|
+
* caused it. The route DID validate (`navItemSchema` /
|
|
6
|
+
* `updateNavItemSchema` in `api/admin/navigation`), so nothing invalid
|
|
7
|
+
* was ever stored; the gap was purely that the admin was told badly.
|
|
8
|
+
* WHAT: The nav-item form schema, shared by the editor and the routes so the
|
|
9
|
+
* two cannot disagree about what a valid nav item is.
|
|
10
|
+
*
|
|
11
|
+
* ## `href` is checked against real routes only loosely
|
|
12
|
+
*
|
|
13
|
+
* A nav item pointing at a page that does not exist is a real defect —
|
|
14
|
+
* `audit-nav-page-wiring` blocks it — but that check needs the filesystem and
|
|
15
|
+
* therefore cannot run in a browser. Here the rule is only "shaped like a
|
|
16
|
+
* path"; the audit remains the authority on whether the path resolves.
|
|
17
|
+
*
|
|
18
|
+
* EXPORTS:
|
|
19
|
+
* navItemFormSchema, navItemCreateSchema, navItemUpdateSchema,
|
|
20
|
+
* type NavItemFormValues
|
|
21
|
+
*
|
|
22
|
+
* @tag domain:admin,navigation
|
|
23
|
+
* @tag layer:schema
|
|
24
|
+
* @tag pattern:none
|
|
25
|
+
* @tag access:isomorphic
|
|
26
|
+
* @tag consumers:AdminNavEditorView,/api/admin/navigation
|
|
27
|
+
* @tag sideEffects:none
|
|
28
|
+
*/
|
|
29
|
+
import { z } from "zod";
|
|
30
|
+
import { annotate } from "../../shell/field-ui-meta";
|
|
31
|
+
/**
|
|
32
|
+
* A path or an absolute URL.
|
|
33
|
+
*
|
|
34
|
+
* Hoisted for readability only. It was originally inlined, and that surfaced a
|
|
35
|
+
* real bug in `audit-field-ui-meta`: its comment-stripper did not understand
|
|
36
|
+
* regex literals, so the trailing `\/\/` of this pattern read as the start of
|
|
37
|
+
* a line comment and blanked the rest of the line — unbalancing the brace walk
|
|
38
|
+
* and reporting a false NO_SECTION. The audit now handles regex literals; this
|
|
39
|
+
* stays hoisted because it reads better, not because it has to.
|
|
40
|
+
*/
|
|
41
|
+
const hrefSchema = z
|
|
42
|
+
.string()
|
|
43
|
+
.min(1, "A destination is required.")
|
|
44
|
+
.refine((v) => v.startsWith("/") || /^https?:\/\//.test(v), {
|
|
45
|
+
message: "Use a path starting with / or a full http(s) URL.",
|
|
46
|
+
});
|
|
47
|
+
/**
|
|
48
|
+
* 🛑 `annotate()` must be the OUTERMOST call on each field — it keys a WeakMap
|
|
49
|
+
* by schema instance and every zod wrapper returns a new one.
|
|
50
|
+
*/
|
|
51
|
+
export const navItemFormSchema = z.object({
|
|
52
|
+
label: annotate(z.string().min(1, "A label is required.").max(60, "Keep the label under 60 characters."), {
|
|
53
|
+
section: "basics", sectionLabel: "Nav item", sectionRequired: true,
|
|
54
|
+
quick: true, order: 1, row: "pair",
|
|
55
|
+
}),
|
|
56
|
+
href: annotate(hrefSchema, { section: "basics", quick: true, order: 2, row: "pair" }),
|
|
57
|
+
icon: annotate(z.string().max(60).optional(), {
|
|
58
|
+
section: "basics", order: 3, row: "pair",
|
|
59
|
+
}),
|
|
60
|
+
order: annotate(z.coerce.number().int("Order must be a whole number.").min(0, "Order cannot be negative.").optional(), { section: "placement", sectionLabel: "Placement", order: 1, row: "pair" }),
|
|
61
|
+
parentId: annotate(z.string().max(80).optional(), {
|
|
62
|
+
section: "placement", order: 2, row: "pair",
|
|
63
|
+
help: "Leave blank for a top-level item.",
|
|
64
|
+
}),
|
|
65
|
+
isVisible: annotate(z.boolean().optional(), {
|
|
66
|
+
section: "placement", order: 3, row: "quarter",
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
/** Create contract — `label` and `href` are what make an item usable at all. */
|
|
70
|
+
export const navItemCreateSchema = navItemFormSchema;
|
|
71
|
+
/** Update contract — every field optional. */
|
|
72
|
+
export const navItemUpdateSchema = navItemFormSchema.partial();
|
|
@@ -14,16 +14,31 @@ export interface BuyNowAuctionInput {
|
|
|
14
14
|
}
|
|
15
15
|
export interface BuyNowAuctionResult {
|
|
16
16
|
/**
|
|
17
|
-
* Buy-Now
|
|
18
|
-
*
|
|
19
|
-
* completes
|
|
20
|
-
*
|
|
21
|
-
*
|
|
17
|
+
* Buy-Now creates neither an order nor a sale. It places a real BID at the
|
|
18
|
+
* listing's buy-now price and writes a LOCKED CART LINE pointing at it; the
|
|
19
|
+
* buyer then completes a normal order through the auction checkout lane, and
|
|
20
|
+
* only THAT closes the auction (see `claimAuctionForCheckout`).
|
|
21
|
+
*
|
|
22
|
+
* Two earlier shapes were both wrong. The original created an order document
|
|
23
|
+
* no orders UI could render and no payment path could reach (see
|
|
24
|
+
* auctionSettlement.ts). Its replacement wrote a locked cart line with no
|
|
25
|
+
* `bidId`, which every downstream sweep filters on — so the line could never
|
|
26
|
+
* lapse, could never be removed (it is `locked: true`), and permanently
|
|
27
|
+
* blocked the buyer's other two lanes.
|
|
28
|
+
*
|
|
29
|
+
* The auction itself is deliberately left ALONE here. It keeps running, keeps
|
|
30
|
+
* taking bids, and keeps accepting other buyouts. Nothing is sold until
|
|
31
|
+
* somebody pays, and which of several racing buyers that is gets decided by a
|
|
32
|
+
* transaction at order time rather than by a first-come lock here.
|
|
22
33
|
*/
|
|
23
34
|
cartLocked: true;
|
|
24
35
|
productId: string;
|
|
25
36
|
amount: number;
|
|
26
37
|
currency: string;
|
|
38
|
+
/** The bid placed at the buy-now price. Won only once the order completes. */
|
|
39
|
+
bidId: string;
|
|
40
|
+
/** When this claim lapses — 1h, clamped to the auction's own end time. */
|
|
41
|
+
checkoutDeadline: Date;
|
|
27
42
|
/** Where to send the buyer to actually pay. */
|
|
28
43
|
checkoutUrl: string;
|
|
29
44
|
}
|
|
@@ -17,7 +17,7 @@ import { storeRepository } from "../../stores/repository/store.repository";
|
|
|
17
17
|
import { siteSettingsRepository } from "../../../repositories";
|
|
18
18
|
import { getAdminRealtimeDb } from "../../../providers/db-firebase";
|
|
19
19
|
import { maskPublicBid } from "../../../security";
|
|
20
|
-
import { resolveMinBid, resolveMinBidIncrement } from "../../../_internal/shared/features/auctions/config";
|
|
20
|
+
import { isBuyNowAvailable, resolveMinBid, resolveMinBidIncrement, } from "../../../_internal/shared/features/auctions/config";
|
|
21
21
|
import { sendNotification } from "../../admin/actions/notification-actions";
|
|
22
22
|
import { BID_MESSAGES } from "../../../_internal/server/jobs/handlers/messages";
|
|
23
23
|
import { ERROR_MESSAGES, AuthorizationError, ValidationError, NotFoundError, } from "../../../errors";
|
|
@@ -25,13 +25,7 @@ import { BID_ERROR_CODES } from "../../../errors/error-codes";
|
|
|
25
25
|
import { increment } from "../../../contracts/field-ops";
|
|
26
26
|
import { getDefaultCurrency } from "../../../core/baseline-resolver";
|
|
27
27
|
import { cartRepository } from "../../cart/repository/cart.repository";
|
|
28
|
-
import {
|
|
29
|
-
import { CART_LANE, LANE_CHECKOUT_WINDOW_MS } from "../../../_internal/shared/checkout/lanes";
|
|
30
|
-
/**
|
|
31
|
-
* Buy-Now and auction settlement give the buyer the same 48h window to pay for
|
|
32
|
-
* a locked line. Kept identical on purpose — both are "you now owe this".
|
|
33
|
-
*/
|
|
34
|
-
const AUCTION_CHECKOUT_WINDOW_MS = LANE_CHECKOUT_WINDOW_MS.auction;
|
|
28
|
+
import { AUCTION_BUYOUT_WINDOW_MINUTES, AUCTION_BUYOUT_WINDOW_MS, AUCTION_CHECKOUT_URL, } from "../../../_internal/shared/checkout/lanes";
|
|
35
29
|
import { resolveDate } from "../../../utils";
|
|
36
30
|
// --- Domain Functions ----------------------------------------------------------
|
|
37
31
|
export async function placeBid(userId, userEmail, input) {
|
|
@@ -260,17 +254,62 @@ export async function buyNowAuction(userId, userName, userEmail, input) {
|
|
|
260
254
|
if (buyNowPrice === null || buyNowPrice <= 0) {
|
|
261
255
|
throw new ValidationError(ERROR_MESSAGES.BID.BUY_NOW_NO_PRICE, { code: BID_ERROR_CODES.BUY_NOW_NO_PRICE });
|
|
262
256
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
await
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
257
|
+
// The standing price this buyout has to beat. Read from the bids as well as
|
|
258
|
+
// the product because `product.currentBid` is a denormalised mirror, and a
|
|
259
|
+
// pending buyout deliberately never writes to it (see BidDocument.isBuyout) —
|
|
260
|
+
// so the bids are the authority here and buyout bids are excluded from it.
|
|
261
|
+
const activeBids = await bidRepository.getActiveByProduct(productId);
|
|
262
|
+
const standingBid = Math.max(product.currentBid ?? 0, ...activeBids.filter((e) => e.data.isBuyout !== true).map((e) => e.data.bidAmount ?? 0), 0);
|
|
263
|
+
// Replaces the old `bidsHaveStarted` gate. Buy Now used to vanish the instant
|
|
264
|
+
// anybody bid, eBay-style, which hid it on almost every real auction. What
|
|
265
|
+
// actually matters is whether it is still a better deal than the bidding.
|
|
266
|
+
if (!isBuyNowAvailable({ buyNowPrice, currentBid: standingBid, isEnded: false, isSold: product.isSold })) {
|
|
267
|
+
throw new ValidationError(ERROR_MESSAGES.BID.BUY_NOW_UNAVAILABLE, {
|
|
268
|
+
code: BID_ERROR_CODES.BUY_NOW_UNAVAILABLE,
|
|
272
269
|
});
|
|
270
|
+
}
|
|
271
|
+
// Idempotency, NOT exclusivity. Re-clicking Buy Now (or a double submit) must
|
|
272
|
+
// not stack a second hold on the same buyer — but a DIFFERENT buyer holding
|
|
273
|
+
// one is fine and deliberate: whoever pays first gets it.
|
|
274
|
+
const existingHold = activeBids.find((e) => e.data.isBuyout === true && e.data.userId === userId);
|
|
275
|
+
if (existingHold) {
|
|
276
|
+
return {
|
|
277
|
+
cartLocked: true,
|
|
278
|
+
productId,
|
|
279
|
+
amount: existingHold.data.bidAmount,
|
|
280
|
+
currency: existingHold.data.currency,
|
|
281
|
+
bidId: existingHold.data.id,
|
|
282
|
+
checkoutDeadline: new Date(Date.now() + AUCTION_BUYOUT_WINDOW_MS),
|
|
283
|
+
checkoutUrl: AUCTION_CHECKOUT_URL,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
// The product's own currency, not the site default — the cart line, the bid
|
|
287
|
+
// and the listing all have to agree, and settlement uses the bid's currency.
|
|
288
|
+
const currency = product.currency || getDefaultCurrency();
|
|
289
|
+
const profile = await userRepository.findById(userId);
|
|
290
|
+
// A real bid at the buy-now price — this is what "the bid placed is the
|
|
291
|
+
// buyout price by the buyer" means. It starts `active`/`isWinning: false`
|
|
292
|
+
// like any other bid (bidRepository.create forces both) and is promoted to
|
|
293
|
+
// `won` by claimAuctionForCheckout when the order actually lands.
|
|
294
|
+
const bid = await bidRepository.create({
|
|
295
|
+
productId,
|
|
296
|
+
productTitle: product.title,
|
|
297
|
+
userId,
|
|
298
|
+
userName: profile?.displayName ?? userName ?? userEmail ?? "Anonymous",
|
|
299
|
+
userEmail: profile?.email ?? userEmail ?? "",
|
|
300
|
+
bidAmount: buyNowPrice,
|
|
301
|
+
currency,
|
|
302
|
+
bidDate: new Date(),
|
|
303
|
+
autoMaxBid: buyNowPrice,
|
|
304
|
+
isBuyout: true,
|
|
273
305
|
});
|
|
306
|
+
// Clamped to the auction's own end: a buyout that hasn't been paid for by the
|
|
307
|
+
// time the clock runs out fails anyway, because settlement will have awarded
|
|
308
|
+
// the item to whoever actually won the bidding. Showing a deadline past that
|
|
309
|
+
// point would promise a window that does not exist.
|
|
310
|
+
const auctionEnd = product.auctionEndDate ? resolveDate(product.auctionEndDate) : null;
|
|
311
|
+
const softDeadline = Date.now() + AUCTION_BUYOUT_WINDOW_MS;
|
|
312
|
+
const checkoutDeadline = new Date(auctionEnd ? Math.min(softDeadline, auctionEnd.getTime()) : softDeadline);
|
|
274
313
|
// Locked cart line, not an order — see BuyNowAuctionResult above. Written
|
|
275
314
|
// through the repository directly (auctions are canAddToCart:false for
|
|
276
315
|
// user-initiated adds), the same deliberate bypass checkoutOffer uses.
|
|
@@ -285,18 +324,52 @@ export async function buyNowAuction(userId, userName, userEmail, input) {
|
|
|
285
324
|
storeName: product.storeName ?? "",
|
|
286
325
|
listingType: "auction",
|
|
287
326
|
isAuctionWin: true,
|
|
327
|
+
isBuyout: true,
|
|
288
328
|
auctionId: productId,
|
|
329
|
+
bidId: bid.id,
|
|
289
330
|
lockedPrice: buyNowPrice,
|
|
290
|
-
checkoutDeadline
|
|
331
|
+
checkoutDeadline,
|
|
291
332
|
locked: true,
|
|
292
333
|
});
|
|
293
|
-
|
|
334
|
+
// Best-effort, exactly like placeBid's outbid notice: the hold is already
|
|
335
|
+
// committed and a failed notification must never undo it.
|
|
336
|
+
try {
|
|
337
|
+
await sendNotification({
|
|
338
|
+
userId,
|
|
339
|
+
type: "bid_won",
|
|
340
|
+
priority: "high",
|
|
341
|
+
title: BID_MESSAGES.BUY_NOW_HELD_TITLE,
|
|
342
|
+
message: BID_MESSAGES.BUY_NOW_HELD_MESSAGE(product.title, currency, buyNowPrice, AUCTION_BUYOUT_WINDOW_MINUTES),
|
|
343
|
+
relatedId: productId,
|
|
344
|
+
relatedType: "product",
|
|
345
|
+
actionUrl: AUCTION_CHECKOUT_URL,
|
|
346
|
+
actionLabel: BID_MESSAGES.BUY_NOW_HELD_ACTION,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
catch (notifyErr) {
|
|
350
|
+
void normalizeError(notifyErr);
|
|
351
|
+
serverLogger.warn("buyNowAuction: hold notification failed", {
|
|
352
|
+
error: notifyErr,
|
|
353
|
+
productId,
|
|
354
|
+
userId,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
294
357
|
serverLogger.info("buyNowAuction", {
|
|
295
358
|
productId,
|
|
296
359
|
userId,
|
|
360
|
+
bidId: bid.id,
|
|
297
361
|
amount: buyNowPrice,
|
|
362
|
+
checkoutDeadline: checkoutDeadline.toISOString(),
|
|
298
363
|
});
|
|
299
|
-
return {
|
|
364
|
+
return {
|
|
365
|
+
cartLocked: true,
|
|
366
|
+
productId,
|
|
367
|
+
amount: buyNowPrice,
|
|
368
|
+
currency,
|
|
369
|
+
bidId: bid.id,
|
|
370
|
+
checkoutDeadline,
|
|
371
|
+
checkoutUrl: AUCTION_CHECKOUT_URL,
|
|
372
|
+
};
|
|
300
373
|
}
|
|
301
374
|
export async function listBidsByProduct(productId, params) {
|
|
302
375
|
const result = await bidRepository.list({
|
|
@@ -14,5 +14,14 @@ export interface AuctionBidsTableProps {
|
|
|
14
14
|
totalPages?: number;
|
|
15
15
|
currentPage?: number;
|
|
16
16
|
onPageChange?: (page: number) => void;
|
|
17
|
+
/**
|
|
18
|
+
* Open a bid. Supplied by the three dashboard bid listings, which were dead
|
|
19
|
+
* ends until W8 — a bid could be seen here and never opened, so its amount,
|
|
20
|
+
* auction, timing and outcome existed only as a table row.
|
|
21
|
+
*
|
|
22
|
+
* Optional because the PUBLIC bid history on an auction page also renders
|
|
23
|
+
* this table, and there a row is not something a visitor may open.
|
|
24
|
+
*/
|
|
25
|
+
onRowClick?: (bid: BidDocument) => void;
|
|
17
26
|
}
|
|
18
|
-
export declare function AuctionBidsTable({ bids, portal, emptyLabel, totalPages, currentPage, onPageChange, }: AuctionBidsTableProps): React.JSX.Element;
|
|
27
|
+
export declare function AuctionBidsTable({ bids, portal, emptyLabel, totalPages, currentPage, onPageChange, onRowClick, }: AuctionBidsTableProps): React.JSX.Element;
|
|
@@ -52,7 +52,22 @@ function groupByAuction(bids) {
|
|
|
52
52
|
}
|
|
53
53
|
return Array.from(map.values());
|
|
54
54
|
}
|
|
55
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Enter/Space activate a non-button element that behaves as one.
|
|
57
|
+
*
|
|
58
|
+
* A named helper rather than an inline closure: the inline form nested six
|
|
59
|
+
* braces deep inside the row's JSX, which is both unreadable and what
|
|
60
|
+
* `audit-code-quality`'s DEEP_NESTING rule exists to prevent.
|
|
61
|
+
*/
|
|
62
|
+
function activateOnKey(run) {
|
|
63
|
+
return (e) => {
|
|
64
|
+
if (e.key !== "Enter" && e.key !== " ")
|
|
65
|
+
return;
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
run();
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function AuctionRow({ auction, portal, onRowClick, }) {
|
|
56
71
|
const [expanded, setExpanded] = useState(false);
|
|
57
72
|
// Most-recent bid first — matches the caller's own newest-first ordering
|
|
58
73
|
// (see UserBidsPage) instead of re-sorting by amount, which silently threw
|
|
@@ -60,14 +75,17 @@ function AuctionRow({ auction, portal, }) {
|
|
|
60
75
|
const sorted = useMemo(() => [...auction.bids].sort((a, b) => +new Date(b.bidDate) - +new Date(a.bidDate)), [auction.bids]);
|
|
61
76
|
const highest = auction.bids.reduce((max, b) => Math.max(max, b.bidAmount), 0);
|
|
62
77
|
const isWinning = auction.bids.some((b) => b.isWinning);
|
|
63
|
-
return (_jsxs(Div, { className: `border border-[var(--appkit-color-border)] ${__O.hidden} bg-[var(--appkit-color-surface)]`, rounded: "xl", shadow: "sm", children: [_jsxs(Button, { type: "button", variant: "ghost", onClick: () => setExpanded((v) => !v), gap: "lg", paddingX: "md", paddingY: "md", className: "w-full text-left", "aria-expanded": expanded, children: [_jsx(ChevronRight, { className: `shrink-0 text-[var(--appkit-color-text-muted)] transition-transform ${expanded ? "rotate-90" : ""}`, size: 16 }), _jsx(Div, { className: "flex-1 min-w-0", children: _jsx(Text, { className: "text-[var(--appkit-color-text)] line-clamp-1", size: "sm", weight: "semibold", children: auction.productTitle }) }), _jsxs(Row, { gap: "sm", className: "shrink-0", children: [isWinning && (_jsx(Badge, { variant: "active", children: "Winning" })), _jsxs(Text, { variant: "secondary", size: "xs", children: [auction.bids.length, " bid", auction.bids.length !== 1 ? "s" : ""] }), _jsx(Text, { className: "text-[var(--appkit-color-text)]", size: "sm", weight: "semibold", children: formatBidAmount(highest) })] })] }), expanded && (_jsxs(Div, { className: "border-t border-[var(--appkit-color-border)]", children: [_jsxs(Div, { textSize: "xs", textWeight: "medium", paddingX: "x-md", paddingY: "y-xs", className: `grid text-[var(--appkit-color-text-muted)] uppercase tracking-wide border-b border-[var(--appkit-color-border-subtle)] ${portal === "buyer" ? "[grid-template-columns:1fr_auto_auto]" : "[grid-template-columns:1fr_1fr_auto_auto]"}`, children: [_jsx(Span, { children: portal === "buyer" ? "Amount" : "Bidder" }), portal !== "buyer" && _jsx(Span, { children: "Amount" }), _jsx(Span, { children: "Status" }), _jsx(Span, { className: "text-right", children: "Time" })] }), sorted.map((bid) => (_jsxs(Div, { layout: "grid", align: "center", paddingX: "x-md", paddingY: "y-xs-tall",
|
|
78
|
+
return (_jsxs(Div, { className: `border border-[var(--appkit-color-border)] ${__O.hidden} bg-[var(--appkit-color-surface)]`, rounded: "xl", shadow: "sm", children: [_jsxs(Button, { type: "button", variant: "ghost", onClick: () => setExpanded((v) => !v), gap: "lg", paddingX: "md", paddingY: "md", className: "w-full text-left", "aria-expanded": expanded, children: [_jsx(ChevronRight, { className: `shrink-0 text-[var(--appkit-color-text-muted)] transition-transform ${expanded ? "rotate-90" : ""}`, size: 16 }), _jsx(Div, { className: "flex-1 min-w-0", children: _jsx(Text, { className: "text-[var(--appkit-color-text)] line-clamp-1", size: "sm", weight: "semibold", children: auction.productTitle }) }), _jsxs(Row, { gap: "sm", className: "shrink-0", children: [isWinning && (_jsx(Badge, { variant: "active", children: "Winning" })), _jsxs(Text, { variant: "secondary", size: "xs", children: [auction.bids.length, " bid", auction.bids.length !== 1 ? "s" : ""] }), _jsx(Text, { className: "text-[var(--appkit-color-text)]", size: "sm", weight: "semibold", children: formatBidAmount(highest) })] })] }), expanded && (_jsxs(Div, { className: "border-t border-[var(--appkit-color-border)]", children: [_jsxs(Div, { textSize: "xs", textWeight: "medium", paddingX: "x-md", paddingY: "y-xs", className: `grid text-[var(--appkit-color-text-muted)] uppercase tracking-wide border-b border-[var(--appkit-color-border-subtle)] ${portal === "buyer" ? "[grid-template-columns:1fr_auto_auto]" : "[grid-template-columns:1fr_1fr_auto_auto]"}`, children: [_jsx(Span, { children: portal === "buyer" ? "Amount" : "Bidder" }), portal !== "buyer" && _jsx(Span, { children: "Amount" }), _jsx(Span, { children: "Status" }), _jsx(Span, { className: "text-right", children: "Time" })] }), sorted.map((bid) => (_jsxs(Div, { layout: "grid", align: "center", paddingX: "x-md", paddingY: "y-xs-tall",
|
|
79
|
+
// Keyboard-reachable only when it is actually interactive —
|
|
80
|
+
// a row with no handler must not be a tab stop.
|
|
81
|
+
role: onRowClick ? "button" : undefined, tabIndex: onRowClick ? 0 : undefined, onClick: onRowClick ? () => onRowClick(bid) : undefined, onKeyDown: onRowClick ? activateOnKey(() => onRowClick(bid)) : undefined, className: `border-b border-[var(--appkit-color-border-subtle)] last:border-0 hover:bg-[var(--appkit-color-border-subtle)] transition-colors ${onRowClick ? "cursor-pointer" : ""} ${portal === "buyer" ? "[grid-template-columns:1fr_auto_auto]" : "[grid-template-columns:1fr_1fr_auto_auto]"}`, children: [portal !== "buyer" && (_jsx(Text, { className: "text-[var(--appkit-color-text)] truncate pr-[0.75rem]", size: "sm", children: bid.userName || bid.userId })), _jsx(Text, { className: "text-[var(--appkit-color-text)]", size: "sm", weight: "medium", children: formatBidAmount(bid.bidAmount) }), _jsx(Badge, { variant: STATUS_VARIANT[bid.status] ?? "pending", className: "capitalize", children: bid.status }), _jsxs(Stack, { gap: "xs", align: "end", children: [_jsx(Text, { variant: "secondary", size: "xs", align: "end", children: relDate(bid.bidDate) }), portal === "buyer" && bid.status === "won" && (_jsx(TextLink, { href: bid.orderId
|
|
64
82
|
? String(ROUTES.USER.ORDER_DETAIL(bid.orderId))
|
|
65
83
|
: `${String(ROUTES.USER.CHECKOUT)}?lane=${CART_LANE.AUCTION}`, size: "xs", weight: "semibold", children: bid.orderId ? "View order" : "Pay now →" }))] })] }, bid.id)))] }))] }));
|
|
66
84
|
}
|
|
67
|
-
export function AuctionBidsTable({ bids, portal = "buyer", emptyLabel = "No bids found.", totalPages, currentPage, onPageChange, }) {
|
|
85
|
+
export function AuctionBidsTable({ bids, portal = "buyer", emptyLabel = "No bids found.", totalPages, currentPage, onPageChange, onRowClick, }) {
|
|
68
86
|
const auctions = useMemo(() => groupByAuction(bids), [bids]);
|
|
69
87
|
if (auctions.length === 0) {
|
|
70
88
|
return (_jsx(Div, { padding: "y-6xl", className: "text-left", children: _jsx(Text, { variant: "secondary", children: emptyLabel }) }));
|
|
71
89
|
}
|
|
72
|
-
return (_jsxs(Stack, { gap: "3", children: [auctions.map((auction) => (_jsx(AuctionRow, { auction: auction, portal: portal }, auction.productId))), totalPages !== undefined && totalPages > 1 && currentPage !== undefined && onPageChange && (_jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: onPageChange }) }))] }));
|
|
90
|
+
return (_jsxs(Stack, { gap: "3", children: [auctions.map((auction) => (_jsx(AuctionRow, { auction: auction, portal: portal, onRowClick: onRowClick }, auction.productId))), totalPages !== undefined && totalPages > 1 && currentPage !== undefined && onPageChange && (_jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: onPageChange }) }))] }));
|
|
73
91
|
}
|
|
@@ -15,7 +15,7 @@ import { formatCurrency } from "../../../utils/number.formatter";
|
|
|
15
15
|
import { normalizeRichTextHtml } from "../../../utils/string.formatter";
|
|
16
16
|
import { safeDisplayName } from "../../../security";
|
|
17
17
|
import { getSiteSettingsGlobal } from "../../admin/utils/getSiteSettingsGlobal";
|
|
18
|
-
import { resolveMinBid, resolveMinBidIncrement, } from "../../../_internal/shared/features/auctions/config";
|
|
18
|
+
import { isBuyNowAvailable, resolveMinBid, resolveMinBidIncrement, } from "../../../_internal/shared/features/auctions/config";
|
|
19
19
|
import { Button, Container, CountdownDisplay, Div, Heading, Input, Main, Nav, RichText, Row, Section, Span, Stack, Text, Ul, Li, Dl, Dt, Dd, } from "../../../ui";
|
|
20
20
|
import { AuctionDetailView } from "../../products/components/AuctionDetailView";
|
|
21
21
|
import { ProductTabsShell } from "../../products/components/ProductTabsShell";
|
|
@@ -45,8 +45,8 @@ function toDescriptionHtml(raw) {
|
|
|
45
45
|
return normalizeRichTextHtml(s);
|
|
46
46
|
}
|
|
47
47
|
function renderAuctionInfoPanel(props) {
|
|
48
|
-
const { productId, title, currentBid, currency, bidCount, isEnded, endDate, buyNowPrice, featured, freeShipping, condition, category, categoryName, brand, brandSlug, productFeatures, features, descriptionHtml, safeSeller, storeHref } = props;
|
|
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 &&
|
|
48
|
+
const { productId, title, currentBid, currency, bidCount, isEnded, endDate, buyNowPrice, buyNowAvailable, featured, freeShipping, condition, category, categoryName, brand, brandSlug, productFeatures, features, descriptionHtml, safeSeller, storeHref } = props;
|
|
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()] })] }), buyNowAvailable && buyNowPrice !== null && (_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" })] }) }))] }));
|
|
50
50
|
}
|
|
51
51
|
/**
|
|
52
52
|
* Store reviews for the auction's seller.
|
|
@@ -98,7 +98,12 @@ export async function AuctionDetailPageView({ id, initialAuction, onPlaceBid, on
|
|
|
98
98
|
const isEnded = endDate ? endDate < new Date() : false;
|
|
99
99
|
const bidCount = typeof p.bidCount === "number" ? p.bidCount : 0;
|
|
100
100
|
const buyNowPrice = typeof p.buyNowPrice === "number" ? p.buyNowPrice : null;
|
|
101
|
-
const
|
|
101
|
+
const buyNowAvailable = isBuyNowAvailable({
|
|
102
|
+
buyNowPrice,
|
|
103
|
+
currentBid,
|
|
104
|
+
isEnded,
|
|
105
|
+
isSold: p.isSold === true,
|
|
106
|
+
});
|
|
102
107
|
const condition = typeof p.condition === "string" ? p.condition : null;
|
|
103
108
|
const featured = p.featured === true;
|
|
104
109
|
const shippingPaidBy = p.shippingPaidBy;
|
|
@@ -149,9 +154,9 @@ export async function AuctionDetailPageView({ id, initialAuction, onPlaceBid, on
|
|
|
149
154
|
price: typeof p.price === "number" ? p.price : undefined,
|
|
150
155
|
storeId: typeof p.storeId === "string" ? p.storeId : undefined,
|
|
151
156
|
storeName: typeof p.storeName === "string" ? p.storeName : undefined,
|
|
152
|
-
} }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.AUCTIONS), className: CLS_BREADCRUMB_LINK, children: "Auctions" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(AuctionDetailView, { renderGallery: () => (_jsx(ProductGalleryClient, { images: images, video: productVideo, productName: title })), renderInfo: () => renderAuctionInfoPanel({ productId: String(product.id), title, currentBid, currency, bidCount, isEnded, endDate, buyNowPrice, featured, freeShipping, condition, category, categoryName, brand, brandSlug, productFeatures, features, descriptionHtml, safeSeller, storeHref }), renderBidForm: () => onPlaceBid ? (_jsx(Stack, { id: "auction-bid-form", gap: "3", children: _jsxs(Stack, { className: `${__P.p5}`, border: "subtle", gap: "3", rounded: "xl", surface: "muted", children: [_jsxs(Row, { justify: "between", align: "baseline", children: [_jsx(LiveBidPrice, { productId: String(product.id), currentBid: currentBid, bidCount: bidCount, currency: currency, isEnded: isEnded, priceSize: "xl", badgeSize: "xs" }), !isEnded && (_jsx(LiveMinIncrement, { productId: String(product.id), currentBid: currentBid, bidCount: bidCount, currency: currency, isEnded: isEnded, tiers: bidIncrementTiers, minBidIncrementOverride: minBidIncrementOverride }))] }), !isEnded && endDate && (_jsxs(Text, { align: "center", size: "xs", color: "muted", children: ["Ends in ", _jsx(CountdownDisplay, { targetDate: endDate, format: "auto", expiredLabel: "Ended" })] })), _jsx(PlaceBidModalButton, { productId: String(product.id), currentBid: currentBid, startingBid: startingBid, tiers: bidIncrementTiers, minBidIncrementOverride: minBidIncrementOverride, currency: currency, isEnded: isEnded, auctionEndDate: endDate, buyNowPrice: buyNowPrice,
|
|
157
|
+
} }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.AUCTIONS), className: CLS_BREADCRUMB_LINK, children: "Auctions" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(AuctionDetailView, { renderGallery: () => (_jsx(ProductGalleryClient, { images: images, video: productVideo, productName: title })), renderInfo: () => renderAuctionInfoPanel({ productId: String(product.id), title, currentBid, currency, bidCount, isEnded, endDate, buyNowPrice, buyNowAvailable, featured, freeShipping, condition, category, categoryName, brand, brandSlug, productFeatures, features, descriptionHtml, safeSeller, storeHref }), renderBidForm: () => onPlaceBid ? (_jsx(Stack, { id: "auction-bid-form", gap: "3", children: _jsxs(Stack, { className: `${__P.p5}`, border: "subtle", gap: "3", rounded: "xl", surface: "muted", children: [_jsxs(Row, { justify: "between", align: "baseline", children: [_jsx(LiveBidPrice, { productId: String(product.id), currentBid: currentBid, bidCount: bidCount, currency: currency, isEnded: isEnded, priceSize: "xl", badgeSize: "xs" }), !isEnded && (_jsx(LiveMinIncrement, { productId: String(product.id), currentBid: currentBid, bidCount: bidCount, currency: currency, isEnded: isEnded, tiers: bidIncrementTiers, minBidIncrementOverride: minBidIncrementOverride }))] }), !isEnded && endDate && (_jsxs(Text, { align: "center", size: "xs", color: "muted", children: ["Ends in ", _jsx(CountdownDisplay, { targetDate: endDate, format: "auto", expiredLabel: "Ended" })] })), _jsx(PlaceBidModalButton, { productId: String(product.id), currentBid: currentBid, startingBid: startingBid, tiers: bidIncrementTiers, minBidIncrementOverride: minBidIncrementOverride, currency: currency, isEnded: isEnded, auctionEndDate: endDate, buyNowPrice: buyNowPrice, bidCount: bidCount, tags: tags, onPlaceBid: onPlaceBid, onBuyNow: onBuyNow, triggerClassName: "w-full" })] }) })) : (
|
|
153
158
|
/* Read-only bid panel — shown when no bid action is wired (preview/demo) */
|
|
154
|
-
_jsxs(Stack, { className: `${__P.p5}`, border: "subtle", gap: "md", rounded: "xl", surface: "muted", children: [_jsx(Stack, { gap: "xs", children: _jsxs(Text, { size: "xs", color: "muted", children: ["Starting bid: ", formatCurrency(startingBid, currency), !isEnded && _jsxs(_Fragment, { children: [" \u00B7 min increment ", formatCurrency(initialMinBidIncrement, currency)] })] }) }), _jsxs(Stack, { gap: "sm", children: [_jsx(Input, { type: "number", placeholder: `At least ${formatCurrency(initialMinBid, currency)}`, min: initialMinBid, "aria-label": "Your bid amount", disabled: isEnded }), !isEnded && endDate && (_jsxs(Text, { align: "center", size: "xs", color: "muted", children: ["Ends in ", _jsx(CountdownDisplay, { targetDate: endDate, format: "auto", expiredLabel: "Ended" })] })), _jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: isEnded, children: isEnded ? "Auction Ended" : "Place Bid" }), buyNowPrice !== null &&
|
|
159
|
+
_jsxs(Stack, { className: `${__P.p5}`, border: "subtle", gap: "md", rounded: "xl", surface: "muted", children: [_jsx(Stack, { gap: "xs", children: _jsxs(Text, { size: "xs", color: "muted", children: ["Starting bid: ", formatCurrency(startingBid, currency), !isEnded && _jsxs(_Fragment, { children: [" \u00B7 min increment ", formatCurrency(initialMinBidIncrement, currency)] })] }) }), _jsxs(Stack, { gap: "sm", children: [_jsx(Input, { type: "number", placeholder: `At least ${formatCurrency(initialMinBid, currency)}`, min: initialMinBid, "aria-label": "Your bid amount", disabled: isEnded }), !isEnded && endDate && (_jsxs(Text, { align: "center", size: "xs", color: "muted", children: ["Ends in ", _jsx(CountdownDisplay, { targetDate: endDate, format: "auto", expiredLabel: "Ended" })] })), _jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: isEnded, children: isEnded ? "Auction Ended" : "Place Bid" }), buyNowAvailable && buyNowPrice !== null && (_jsxs(Text, { align: "center", size: "xs", color: "muted", children: ["Buy Now available at ", formatCurrency(buyNowPrice, currency)] }))] }), tags.length > 0 && (_jsx(Div, { border: "default", className: "border-t", padding: "t-md", children: _jsx(Row, { wrap: true, gap: "xs", children: tags.map((tag) => (_jsx(Span, { size: "xs", rounded: "full", padding: "pill-sm-tall", surface: "subtle", color: "muted", children: tag }, tag))) }) }))] })), renderMobileBidForm: () => !isEnded && onPlaceBid ? (_jsxs(Stack, { className: `lg:hidden ${__P.p4}`, border: "subtle", gap: "3", rounded: "xl", surface: "muted", children: [_jsx(LiveBidPrice, { productId: String(product.id), currentBid: currentBid, bidCount: bidCount, currency: currency, isEnded: isEnded, priceSize: "base", badgeSize: "xs" }), endDate && (_jsxs(Text, { align: "center", size: "xs", color: "muted", children: ["Ends in ", _jsx(CountdownDisplay, { targetDate: endDate, format: "auto", expiredLabel: "Ended" })] })), _jsx(PlaceBidModalButton, { productId: String(product.id), currentBid: currentBid, startingBid: startingBid, tiers: bidIncrementTiers, minBidIncrementOverride: minBidIncrementOverride, currency: currency, isEnded: isEnded, auctionEndDate: endDate, buyNowPrice: buyNowPrice, bidCount: bidCount, tags: tags, onPlaceBid: onPlaceBid, onBuyNow: onBuyNow, triggerClassName: "w-full" })] })) : !isEnded ? (_jsxs(Div, { className: `${__P.p4} lg:hidden`, border: "subtle", rounded: "xl", surface: "muted", children: [_jsxs(Row, { align: "center", gap: "sm", className: "mb-3", children: [_jsx(Span, { size: "base", weight: "bold", className: "text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { size: "xs", color: "muted", children: [bidCount, " bids"] })] }), endDate && (_jsxs(Text, { align: "center", className: "mb-3", size: "xs", color: "muted", children: ["Ends in ", _jsx(CountdownDisplay, { targetDate: endDate, format: "auto", expiredLabel: "Ended" })] })), _jsx(Button, { variant: "primary", size: "md", className: "w-full", children: "Place Bid" })] })) : null, renderSublistingSection: sublistingCategoryId
|
|
155
160
|
? () => (_jsx(SublistingCarouselSection, { sublistingCategoryId: sublistingCategoryId, currentListingId: String(product.id) }))
|
|
156
161
|
: undefined, renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })) : undefined, specsContent: specs.length > 0 ? (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: specs.map((s, i) => (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-36 flex-shrink-0", color: "primary", weight: "medium", children: s.name }), _jsxs(Dd, { className: "flex-1", color: "muted", children: [s.value, s.unit ? ` ${s.unit}` : ""] })] }, i))) })) : undefined, customTabs: customSections.map((s) => ({
|
|
157
162
|
id: s.id,
|
|
@@ -2,20 +2,19 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Container, Heading, Main, Section } from "../../../ui";
|
|
3
3
|
import { AdSlot } from "../../homepage/components/AdSlot";
|
|
4
4
|
import { AuctionsIndexListing } from "../../products/components/AuctionsIndexListing";
|
|
5
|
-
import { listPublicProducts, parsePublicProductParams, } from "../../../_internal/server/features/products/list-public";
|
|
5
|
+
import { listPublicProducts, parsePublicProductParams, defaultAvailabilityForListingTypes, } from "../../../_internal/server/features/products/list-public";
|
|
6
6
|
const AUCTION_LISTING_TYPES = ["auction"];
|
|
7
7
|
const DEFAULT_PAGE_SIZE = 24;
|
|
8
8
|
const DEFAULT_SORT = "auctionEndDate";
|
|
9
9
|
export async function AuctionsListView({ searchParams = {} }) {
|
|
10
|
-
// Shared with /api/products
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// Firestore rather than filtering in memory.
|
|
10
|
+
// Shared with /api/products, and derived rather than hardcoded — this view
|
|
11
|
+
// carried a literal `hideEndedByDefault: true` until 2026-08-24, which is
|
|
12
|
+
// exactly the mirrored-literal shape Root Cause #30 is written as.
|
|
14
13
|
const initial = await listPublicProducts(parsePublicProductParams(searchParams, {
|
|
15
14
|
listingTypes: AUCTION_LISTING_TYPES,
|
|
16
15
|
pageSize: DEFAULT_PAGE_SIZE,
|
|
17
16
|
sorts: DEFAULT_SORT,
|
|
18
|
-
|
|
17
|
+
...defaultAvailabilityForListingTypes(AUCTION_LISTING_TYPES),
|
|
19
18
|
}));
|
|
20
19
|
return (_jsx(Main, { children: _jsx(Section, { padding: "y-2xl", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 1, className: "mb-8", color: "primary", size: "3xl", weight: "semibold", children: "Live Auctions" }), _jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-6" }), _jsx(AuctionsIndexListing, { initialData: initial }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] }) }) }));
|
|
21
20
|
}
|
|
@@ -22,6 +22,13 @@ export interface MarketplaceAuctionCardData {
|
|
|
22
22
|
status?: string;
|
|
23
23
|
slug?: string;
|
|
24
24
|
buyNowPrice?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Feeds `isBuyNowAvailable` alongside `currentBid`/`auctionEndDate`. Every
|
|
27
|
+
* doc→card mapper must copy it: dropping one field a shared predicate reads
|
|
28
|
+
* is exactly how a card ends up advertising a buyout the detail page refuses
|
|
29
|
+
* (Root Cause #48).
|
|
30
|
+
*/
|
|
31
|
+
isSold?: boolean;
|
|
25
32
|
/** Display name of the auction winner — shown masked when the auction has ended */
|
|
26
33
|
winnerDisplayName?: string;
|
|
27
34
|
}
|
|
@@ -12,6 +12,7 @@ import { ROUTES } from "../../../next";
|
|
|
12
12
|
import { formatCurrency } from "../../../utils";
|
|
13
13
|
import { getDefaultCurrency } from "../../../core/baseline-resolver";
|
|
14
14
|
import { BaseListingCard, Button, Caption, Div, Row, Span, Stack, Text, TextLink } from "../../../ui";
|
|
15
|
+
import { isBuyNowAvailable } from "../../../_internal/shared/features/auctions/config";
|
|
15
16
|
const CLS_STAR_ICON = "h-5 w-5 fill-warning text-warning";
|
|
16
17
|
const CLS_LIVE_BADGE = "rounded-full bg-error-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-error-on-solid";
|
|
17
18
|
const CLS_ENDING_BADGE = "rounded-full bg-warning-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-warning-on-solid";
|
|
@@ -28,7 +29,11 @@ function renderAuctionCardInfoList(props) {
|
|
|
28
29
|
}
|
|
29
30
|
function renderAuctionCardInfoGrid(props) {
|
|
30
31
|
const { product, auctionHref, displayBid, bidCount, hasCurrentBid, isEnded, remaining, resolvedBuyoutPrice, inWishlist, wishlistLoading, wishlistActions, countdownClass, mergedLabels, handleWishlist, handleNavigate } = props;
|
|
31
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Row, { align: "start", gap: "sm", children: [_jsx(TextLink, { href: auctionHref ?? "#", className: "min-w-0 flex-1 leading-snug transition-colors hover:text-primary", color: "primary", size: "sm", weight: "medium", children: product.title }), wishlistActions ? (_jsx(Button, { rounded: "full", type: "button", variant: "ghost", onClick: handleWishlist, disabled: wishlistLoading, "aria-label": inWishlist ? mergedLabels.removeFromWishlist : mergedLabels.addToWishlist, className: "-mt-0.5 p-[0.25rem]", children: _jsx(Heart, { className: `h-4 w-4 ${inWishlist ? CLS_HEART_ON : CLS_HEART_OFF}` }) })) : null] }), _jsxs(Div, { children: [_jsx(Caption, { children: isEnded && hasCurrentBid ? mergedLabels.winningBid : hasCurrentBid ? mergedLabels.currentBid : mergedLabels.startingBid }), _jsx(Text, { className: "leading-none text-primary", size: "base", weight: "bold", children: formatCurrency(displayBid, getDefaultCurrency()) }), isEnded && product.winnerDisplayName && (_jsxs(Caption, { className: "mt-0.5", color: "muted", children: [mergedLabels.wonBy, ": ", maskDisplayName(product.winnerDisplayName)] }))] }), _jsxs(Row, { wrap: true, justify: "between", gap: "sm", className: "gap-x-2 gap-y-1", children: [_jsxs(Div, { layout: "inline-flex", gap: "1", textWeight: "semibold", textSize: "xs", className: `items-[center] ${countdownClass}`, paddingY: "y-2xs", paddingX: "x-xs", rounded: "full", children: [_jsx(Clock, { className: "h-3 w-3" }), _jsx(Span, { children: formatCountdownLabel(remaining, mergedLabels.ended) })] }), _jsx(Caption, { children: mergedLabels.totalBids(bidCount) })] }), _jsx(Row, { wrap: true, gap: "xs", className: "mt-auto", children: isEnded ? (_jsx(Button, { gap: "xs", type: "button", variant: "ghost", size: "sm", textSize: "xs", paddingX: "xs", className: "flex-1 cursor-not-allowed opacity-60", disabled: true, children: _jsx(Span, { children: mergedLabels.ended }) })) : (_jsxs(_Fragment, { children: [_jsxs(Button, { gap: "xs", type: "button", variant: "warning", size: "sm", textSize: "xs", paddingX: "xs", className: "flex-1", onClick: handleNavigate, children: [_jsx(Gavel, { className: "h-3 w-3" }), _jsx(Span, { children: mergedLabels.placeBid })] }), resolvedBuyoutPrice ? (
|
|
32
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Row, { align: "start", gap: "sm", children: [_jsx(TextLink, { href: auctionHref ?? "#", className: "min-w-0 flex-1 leading-snug transition-colors hover:text-primary", color: "primary", size: "sm", weight: "medium", children: product.title }), wishlistActions ? (_jsx(Button, { rounded: "full", type: "button", variant: "ghost", onClick: handleWishlist, disabled: wishlistLoading, "aria-label": inWishlist ? mergedLabels.removeFromWishlist : mergedLabels.addToWishlist, className: "-mt-0.5 p-[0.25rem]", children: _jsx(Heart, { className: `h-4 w-4 ${inWishlist ? CLS_HEART_ON : CLS_HEART_OFF}` }) })) : null] }), _jsxs(Div, { children: [_jsx(Caption, { children: isEnded && hasCurrentBid ? mergedLabels.winningBid : hasCurrentBid ? mergedLabels.currentBid : mergedLabels.startingBid }), _jsx(Text, { className: "leading-none text-primary", size: "base", weight: "bold", children: formatCurrency(displayBid, getDefaultCurrency()) }), isEnded && product.winnerDisplayName && (_jsxs(Caption, { className: "mt-0.5", color: "muted", children: [mergedLabels.wonBy, ": ", maskDisplayName(product.winnerDisplayName)] }))] }), _jsxs(Row, { wrap: true, justify: "between", gap: "sm", className: "gap-x-2 gap-y-1", children: [_jsxs(Div, { layout: "inline-flex", gap: "1", textWeight: "semibold", textSize: "xs", className: `items-[center] ${countdownClass}`, paddingY: "y-2xs", paddingX: "x-xs", rounded: "full", children: [_jsx(Clock, { className: "h-3 w-3" }), _jsx(Span, { children: formatCountdownLabel(remaining, mergedLabels.ended) })] }), _jsx(Caption, { children: mergedLabels.totalBids(bidCount) })] }), _jsx(Row, { wrap: true, gap: "xs", className: "mt-auto", children: isEnded ? (_jsx(Button, { gap: "xs", type: "button", variant: "ghost", size: "sm", textSize: "xs", paddingX: "xs", className: "flex-1 cursor-not-allowed opacity-60", disabled: true, children: _jsx(Span, { children: mergedLabels.ended }) })) : (_jsxs(_Fragment, { children: [_jsxs(Button, { gap: "xs", type: "button", variant: "warning", size: "sm", textSize: "xs", paddingX: "xs", className: "flex-1", onClick: handleNavigate, children: [_jsx(Gavel, { className: "h-3 w-3" }), _jsx(Span, { children: mergedLabels.placeBid })] }), resolvedBuyoutPrice ? (
|
|
33
|
+
/* `secondary`, not `danger` — this button only navigates to the
|
|
34
|
+
PDP, and `danger` is reserved for destructive actions (Rule #7
|
|
35
|
+
kinds). ACTIONS.AUCTION["buy-it-now"] is itself `secondary`. */
|
|
36
|
+
_jsxs(Button, { gap: "xs", type: "button", variant: "secondary", size: "sm", textSize: "xs", paddingX: "xs", className: "flex-1", onClick: handleNavigate, children: [_jsx(ShoppingBag, { className: "h-3 w-3" }), _jsx(Span, { children: mergedLabels.buyout })] })) : null] })) })] }));
|
|
32
37
|
}
|
|
33
38
|
const DEFAULT_LABELS = {
|
|
34
39
|
selectItem: "Select item",
|
|
@@ -107,7 +112,19 @@ export function MarketplaceAuctionCard({ product, buyoutPrice, className = "", v
|
|
|
107
112
|
const bidCount = product.bidCount ?? 0;
|
|
108
113
|
const hasCurrentBid = (product.currentBid ?? 0) > 0;
|
|
109
114
|
const hasVideo = Boolean(product.video?.url);
|
|
110
|
-
|
|
115
|
+
// Availability is folded in HERE rather than at the render site, so the chip
|
|
116
|
+
// can never advertise a buyout the detail page will refuse. It used to gate on
|
|
117
|
+
// the price merely EXISTING, so cards linked through to a PDP with no Buy Now
|
|
118
|
+
// button at all. `isBuyNowAvailable` is the same predicate that page uses.
|
|
119
|
+
const rawBuyoutPrice = buyoutPrice ?? product.buyNowPrice;
|
|
120
|
+
const resolvedBuyoutPrice = isBuyNowAvailable({
|
|
121
|
+
buyNowPrice: rawBuyoutPrice,
|
|
122
|
+
currentBid: product.currentBid,
|
|
123
|
+
isEnded,
|
|
124
|
+
isSold: product.isSold === true,
|
|
125
|
+
})
|
|
126
|
+
? rawBuyoutPrice
|
|
127
|
+
: undefined;
|
|
111
128
|
const images = [product.mainImage, ...(product.images ?? [])].filter(Boolean);
|
|
112
129
|
const currentSrc = images[imageIndex] ?? product.mainImage;
|
|
113
130
|
useEffect(() => {
|