@mohasinac/appkit 4.13.0 → 4.14.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 +6 -0
- 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/checkout/actions.js +6 -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/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/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 +45 -9
- 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 +15 -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/blog/schema.d.ts +2 -2
- package/dist/_internal/shared/features/cart/schema.d.ts +12 -12
- 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 +32 -32
- 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 +66 -0
- package/dist/_internal/shared/history/history.js +158 -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 +7 -5
- package/dist/client.js +2 -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 +49 -2
- package/dist/constants/field-names.js +53 -2
- 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/UserOrdersView.js +7 -0
- 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/AdminClassifiedView.js +8 -3
- package/dist/features/admin/components/AdminDigitalCodesView.js +8 -3
- package/dist/features/admin/components/AdminFeatureFlagsView.js +1 -0
- package/dist/features/admin/components/AdminLiveView.js +8 -3
- package/dist/features/admin/components/AdminOffersView.d.ts +4 -0
- package/dist/features/admin/components/AdminOffersView.js +186 -0
- package/dist/features/admin/components/AdminOrdersView.js +10 -1
- package/dist/features/admin/components/AdminPrizeDrawsView.js +5 -0
- package/dist/features/admin/components/AdminProductsView.js +16 -29
- 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/index.d.ts +2 -0
- package/dist/features/admin/components/index.js +1 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +86 -0
- package/dist/features/admin/constants/filter-tabs.js +55 -0
- package/dist/features/admin/schemas/firestore.d.ts +122 -3
- package/dist/features/admin/schemas/firestore.js +1 -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/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/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/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/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/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/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/index.d.ts +4 -4
- 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 +2 -2
- 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/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 +116 -22
- package/dist/features/orders/schemas/firestore.d.ts +117 -0
- package/dist/features/orders/schemas/firestore.js +30 -0
- package/dist/features/orders/schemas/index.d.ts +18 -18
- 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/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 +66 -66
- package/dist/features/products/schemas/product-features.validators.d.ts +6 -6
- 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/search/schemas/index.d.ts +4 -4
- package/dist/features/seller/actions/offer-actions.js +25 -2
- package/dist/features/seller/components/SellerArtView.js +5 -0
- package/dist/features/seller/components/SellerAuctionsView.js +13 -15
- package/dist/features/seller/components/SellerClassifiedView.js +5 -0
- package/dist/features/seller/components/SellerDigitalCodesView.js +5 -0
- package/dist/features/seller/components/SellerLiveView.js +5 -0
- package/dist/features/seller/components/SellerOrdersView.js +31 -9
- package/dist/features/seller/components/SellerPayoutSettingsView.js +19 -18
- 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 +73 -16
- package/dist/features/seller/repository/offer.repository.js +38 -3
- package/dist/features/seller/schemas/index.d.ts +2 -2
- package/dist/features/seller/schemas/offer-forms.d.ts +25 -0
- package/dist/features/seller/schemas/offer-forms.js +21 -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/entity-form.d.ts +109 -0
- package/dist/features/shell/entity-form.js +96 -0
- package/dist/features/shell/index.d.ts +4 -0
- package/dist/features/shell/index.js +2 -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 +105 -1
- package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +1 -1
- package/dist/index.d.ts +13 -10
- package/dist/index.js +14 -9
- package/dist/next/routing/route-map.d.ts +7 -1
- package/dist/next/routing/route-map.js +5 -0
- 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/registry.d.ts +68 -68
- 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/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 +32 -27
- package/dist/seed/products-art-seed-data.js +50 -12
- package/dist/seed/products-auctions-seed-data.js +97 -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 +4 -3
- package/dist/server.js +8 -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/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 -2
|
@@ -12,6 +12,10 @@ import { CategoryGrid } from "./CategoryGrid";
|
|
|
12
12
|
import { CategoryHighlightsAndFaqSection } from "./CategoryHighlightsAndFaqSection";
|
|
13
13
|
import { GroupedListingsCarousel } from "../../grouped/components/GroupedListingsCarousel";
|
|
14
14
|
import { getGroupsForBrand } from "../../../_internal/server/features/grouped/data";
|
|
15
|
+
import { listingTabCounts, } from "../../../_internal/server/features/products/listing-tab-counts";
|
|
16
|
+
import { CATEGORY_PAGE_TABS } from "../../products/constants/listing-tabs";
|
|
17
|
+
import { enabledCategoryTypes, enabledListingTypes } from "../../../_internal/shared/listing-types/feature-flags";
|
|
18
|
+
import { siteSettingsRepository } from "../../../repositories";
|
|
15
19
|
const __O = {
|
|
16
20
|
hidden: "overflow-hidden",
|
|
17
21
|
};
|
|
@@ -20,7 +24,7 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
|
|
|
20
24
|
.getCategoryBySlug(slug)
|
|
21
25
|
.catch(() => undefined)));
|
|
22
26
|
const brandName = brand?.name;
|
|
23
|
-
const [productsResult,
|
|
27
|
+
const [productsResult, tabCounts, allBundles, activeBrands, groupedListings, settings] = await Promise.all([
|
|
24
28
|
brandName
|
|
25
29
|
? productRepository
|
|
26
30
|
.list({
|
|
@@ -31,36 +35,12 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
|
|
|
31
35
|
})
|
|
32
36
|
.catch(() => null)
|
|
33
37
|
: Promise.resolve(null),
|
|
38
|
+
// One count per tab, derived from CATEGORY_PAGE_TABS. Four of the nine tabs
|
|
39
|
+
// (classifieds, digital codes, live, art) had no count at all and could
|
|
40
|
+
// therefore never hide, however empty the brand was.
|
|
34
41
|
brandName
|
|
35
|
-
?
|
|
36
|
-
|
|
37
|
-
filters: sieveAnd(sieveFilter("status", SIEVE_OP.EQ, "published"), sieveFilter("brand", SIEVE_OP.EQ, brandName), sieveFilter("listingType", SIEVE_OP.EQ, "auction")),
|
|
38
|
-
sorts: sortBy("auctionEndDate", "ASC"),
|
|
39
|
-
page: 1,
|
|
40
|
-
pageSize: 1,
|
|
41
|
-
})
|
|
42
|
-
.catch(() => null)
|
|
43
|
-
: Promise.resolve(null),
|
|
44
|
-
brandName
|
|
45
|
-
? productRepository
|
|
46
|
-
.list({
|
|
47
|
-
filters: sieveAnd(sieveFilter("status", SIEVE_OP.EQ, "published"), sieveFilter("brand", SIEVE_OP.EQ, brandName), sieveFilter("listingType", SIEVE_OP.EQ, "pre-order")),
|
|
48
|
-
sorts: sortBy("createdAt", "DESC"),
|
|
49
|
-
page: 1,
|
|
50
|
-
pageSize: 1,
|
|
51
|
-
})
|
|
52
|
-
.catch(() => null)
|
|
53
|
-
: Promise.resolve(null),
|
|
54
|
-
brandName
|
|
55
|
-
? productRepository
|
|
56
|
-
.list({
|
|
57
|
-
filters: sieveAnd(sieveFilter("status", SIEVE_OP.EQ, "published"), sieveFilter("brand", SIEVE_OP.EQ, brandName), sieveFilter("listingType", SIEVE_OP.EQ, "prize-draw")),
|
|
58
|
-
sorts: sortBy("createdAt", "DESC"),
|
|
59
|
-
page: 1,
|
|
60
|
-
pageSize: 1,
|
|
61
|
-
})
|
|
62
|
-
.catch(() => null)
|
|
63
|
-
: Promise.resolve(null),
|
|
42
|
+
? listingTabCounts(CATEGORY_PAGE_TABS, { brandName })
|
|
43
|
+
: Promise.resolve({}),
|
|
64
44
|
// SB-UNI-D — bundles are categoryType:"bundle" rows on the categories
|
|
65
45
|
// collection, tagged to a brand via the real brandSlug field (added
|
|
66
46
|
// 2026-08-21 — previously a fragile seo.keywords string-match heuristic).
|
|
@@ -74,6 +54,9 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
|
|
|
74
54
|
// Related brands — every other active brand row, excluding this one.
|
|
75
55
|
categoriesRepository.findActiveBrands().catch(() => []),
|
|
76
56
|
brand?.slug ? getGroupsForBrand(brand.slug).catch(() => []) : Promise.resolve([]),
|
|
57
|
+
// Feature flags — accepted by BrandDetailTabs all along but never passed,
|
|
58
|
+
// so a disabled listing type still showed its tab here.
|
|
59
|
+
siteSettingsRepository.findById("global").catch(() => null),
|
|
77
60
|
]);
|
|
78
61
|
const brandBundles = brand?.slug
|
|
79
62
|
? allBundles.filter((b) => b.brandSlug === brand.slug)
|
|
@@ -85,17 +68,17 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
|
|
|
85
68
|
// configured one. The value is consumed as `style={{ backgroundColor }}` so it
|
|
86
69
|
// accepts both CSS vars and raw hex written by an admin.
|
|
87
70
|
const brandColor = brand?.display?.color || "var(--appkit-color-primary)";
|
|
71
|
+
// Keyed by tabSlug. `bundles` is overridden with the brand-scoped figure —
|
|
72
|
+
// `listingTabCounts` cannot narrow bundles by brand (a bundle's brand lives on
|
|
73
|
+
// `brandSlug`, which this page already filters on locally).
|
|
88
74
|
const counts = {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
prizeDraws: prizeDrawsResult?.total ?? 0,
|
|
75
|
+
...tabCounts,
|
|
76
|
+
products: tabCounts.products ?? brand?.metrics?.productCount ?? 0,
|
|
77
|
+
auctions: tabCounts.auctions ?? brand?.metrics?.auctionCount ?? 0,
|
|
93
78
|
bundles: brandBundles.length,
|
|
94
79
|
};
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
counts.bundles;
|
|
100
|
-
return (_jsxs(Main, { children: [_jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-[var(--appkit-color-bg)]"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "absolute inset-0", children: _jsx(MediaImage, { src: coverImage, alt: "", size: "banner" }) }), _jsx(Div, { surface: "overlay-md", className: "absolute inset-0" })] })), !hasCover && (_jsx(DynamicBgDiv, { color: brandColor, className: "absolute inset-0 opacity-10" })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsxs(Nav, { layout: "flex", gap: "2xs", textSize: "sm", className: "mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.BRANDS), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 transition-colors", children: "Brands" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Span, { weight: "medium", className: hasCover ? "text-white" : "text-[var(--appkit-color-text)]", children: brand?.name ?? slug })] }), _jsxs(Row, { className: "mb-3", align: "center", gap: "md", children: [brand?.display?.icon && (_jsx(Span, { className: "leading-none", size: "5xl", children: brand.display.icon })), _jsxs(_Fragment, { children: [_jsx(Heading, { color: "inverse", level: 1, className: `${hasCover ? "" : "text-[var(--appkit-color-text)]"}`, size: "3xl", mdSize: "4xl", weight: "bold", children: brand?.name ?? slug }), brand?.description && typeof brand.description === "string" && !brand.description.startsWith("{") && (_jsx(Text, { color: hasCover ? "inverse" : "muted", className: `max-w-2xl mt-1 ${hasCover ? "/80" : ""}`, size: "base", children: brand.description }))] })] }), _jsxs(Row, { gap: "sm", wrap: true, className: "mt-3", children: [counts.products > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: "inverse", size: "xs", weight: "medium", className: `${hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400"}`, rounded: "full", padding: "pill-sm-tall", children: [counts.products.toLocaleString(), " ", counts.products === 1 ? "product" : "products"] })), counts.auctions > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "warning", surface: hasCover ? undefined : "warning-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [counts.auctions.toLocaleString(), " ", counts.auctions === 1 ? "auction" : "auctions"] })), counts.preOrders > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "info", surface: hasCover ? undefined : "info-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [counts.preOrders.toLocaleString(), " ", counts.preOrders === 1 ? "pre-order" : "pre-orders"] })), totalItems === 0 && (_jsx(Span, { size: "sm", className: hasCover ? "text-white/60" : "text-zinc-400", children: "No items listed yet" }))] })] })] }), (brand?.brandWebsite || brand?.brandCountry || brand?.brandFounded) && (_jsx(Section, { border: "subtle", surface: "default", className: "border-b", padding: "y-md", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "sm", weight: "semibold", color: "muted", transform: "uppercase", children: "About this brand" }), _jsxs(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden max-w-lg", children: [brand?.brandWebsite && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Website" }), _jsx(Dd, { className: "flex-1", children: _jsx(Anchor, { href: brand.brandWebsite, tone: "brand", underline: "hover", children: brand.brandWebsite }) })] })), brand?.brandCountry && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Country" }), _jsx(Dd, { className: "flex-1", color: "muted", children: brand.brandCountry })] })), brand?.brandFounded && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Founded" }), _jsx(Dd, { className: "flex-1", color: "muted", children: brand.brandFounded })] }))] })] }) }) })), _jsx(CategoryHighlightsAndFaqSection, { highlights: brand?.highlights, faqs: brand?.faqs }), _jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: brandName ? (_jsx(BrandDetailTabs, { brandName: brandName, initialProductsData: productsResult ?? undefined, initialBundles: brandBundles, counts: counts })) : (_jsx(Text, { paddingY: "3xl", color: "muted", size: "sm", align: "start", children: "Brand not found." })) }) }), groupedListings.length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(GroupedListingsCarousel, { groups: groupedListings }) }) })), relatedBrands.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-t", padding: "y-lg", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 2, className: "mb-4", size: "xl", weight: "semibold", children: "Related Brands" }), _jsx(CategoryGrid, { categories: relatedBrands, getHref: (b) => String(ROUTES.PUBLIC.BRAND_DETAIL(b.slug)) })] }) }))] }));
|
|
80
|
+
// Header total — `?? 0` is right here (a pill just doesn't render), unlike the
|
|
81
|
+
// tab bar where an unknown count must keep its tab visible.
|
|
82
|
+
const totalItems = Object.values(counts).reduce((sum, n) => sum + (n ?? 0), 0);
|
|
83
|
+
return (_jsxs(Main, { children: [_jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-[var(--appkit-color-bg)]"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "absolute inset-0", children: _jsx(MediaImage, { src: coverImage, alt: "", size: "banner" }) }), _jsx(Div, { surface: "overlay-md", className: "absolute inset-0" })] })), !hasCover && (_jsx(DynamicBgDiv, { color: brandColor, className: "absolute inset-0 opacity-10" })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsxs(Nav, { layout: "flex", gap: "2xs", textSize: "sm", className: "mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.BRANDS), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 transition-colors", children: "Brands" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Span, { weight: "medium", className: hasCover ? "text-white" : "text-[var(--appkit-color-text)]", children: brand?.name ?? slug })] }), _jsxs(Row, { className: "mb-3", align: "center", gap: "md", children: [brand?.display?.icon && (_jsx(Span, { className: "leading-none", size: "5xl", children: brand.display.icon })), _jsxs(_Fragment, { children: [_jsx(Heading, { color: "inverse", level: 1, className: `${hasCover ? "" : "text-[var(--appkit-color-text)]"}`, size: "3xl", mdSize: "4xl", weight: "bold", children: brand?.name ?? slug }), brand?.description && typeof brand.description === "string" && !brand.description.startsWith("{") && (_jsx(Text, { color: hasCover ? "inverse" : "muted", className: `max-w-2xl mt-1 ${hasCover ? "/80" : ""}`, size: "base", children: brand.description }))] })] }), _jsxs(Row, { gap: "sm", wrap: true, className: "mt-3", children: [(counts.products ?? 0) > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: "inverse", size: "xs", weight: "medium", className: `${hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400"}`, rounded: "full", padding: "pill-sm-tall", children: [counts.products.toLocaleString(), " ", counts.products === 1 ? "product" : "products"] })), (counts.auctions ?? 0) > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "warning", surface: hasCover ? undefined : "warning-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [counts.auctions.toLocaleString(), " ", counts.auctions === 1 ? "auction" : "auctions"] })), (counts["pre-orders"] ?? 0) > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "info", surface: hasCover ? undefined : "info-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [counts["pre-orders"].toLocaleString(), " ", counts["pre-orders"] === 1 ? "pre-order" : "pre-orders"] })), totalItems === 0 && (_jsx(Span, { size: "sm", className: hasCover ? "text-white/60" : "text-zinc-400", children: "No items listed yet" }))] })] })] }), (brand?.brandWebsite || brand?.brandCountry || brand?.brandFounded) && (_jsx(Section, { border: "subtle", surface: "default", className: "border-b", padding: "y-md", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "sm", weight: "semibold", color: "muted", transform: "uppercase", children: "About this brand" }), _jsxs(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden max-w-lg", children: [brand?.brandWebsite && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Website" }), _jsx(Dd, { className: "flex-1", children: _jsx(Anchor, { href: brand.brandWebsite, tone: "brand", underline: "hover", children: brand.brandWebsite }) })] })), brand?.brandCountry && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Country" }), _jsx(Dd, { className: "flex-1", color: "muted", children: brand.brandCountry })] })), brand?.brandFounded && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Founded" }), _jsx(Dd, { className: "flex-1", color: "muted", children: brand.brandFounded })] }))] })] }) }) })), _jsx(CategoryHighlightsAndFaqSection, { highlights: brand?.highlights, faqs: brand?.faqs }), _jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: brandName ? (_jsx(BrandDetailTabs, { brandName: brandName, initialProductsData: productsResult ?? undefined, initialBundles: brandBundles, counts: counts, enabledListingTypes: enabledListingTypes(settings), enabledCategoryTypes: enabledCategoryTypes(settings) })) : (_jsx(Text, { paddingY: "3xl", color: "muted", size: "sm", align: "start", children: "Brand not found." })) }) }), groupedListings.length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(GroupedListingsCarousel, { groups: groupedListings }) }) })), relatedBrands.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-t", padding: "y-lg", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 2, className: "mb-4", size: "xl", weight: "semibold", children: "Related Brands" }), _jsx(CategoryGrid, { categories: relatedBrands, getHref: (b) => String(ROUTES.PUBLIC.BRAND_DETAIL(b.slug)) })] }) }))] }));
|
|
101
84
|
}
|
|
@@ -4,13 +4,13 @@ export interface BrandDetailTabsProps {
|
|
|
4
4
|
brandName: string;
|
|
5
5
|
initialProductsData?: any;
|
|
6
6
|
initialBundles?: CategoryDocument[];
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
/**
|
|
8
|
+
* tabSlug -> row count. `undefined` means the count could not be resolved,
|
|
9
|
+
* NOT zero — such a tab stays visible so a failed query can never hide real
|
|
10
|
+
* inventory (Root Cause #59). Keyed by tab id; the previous hand-named shape
|
|
11
|
+
* covered only 5 of 9 tabs (Root Cause #61).
|
|
12
|
+
*/
|
|
13
|
+
counts?: Record<string, number | undefined>;
|
|
14
14
|
/** Enabled listing types (e.g. ["standard","auction"]). When omitted, all tabs shown. */
|
|
15
15
|
enabledListingTypes?: string[];
|
|
16
16
|
/** Enabled category types (e.g. ["category","brand","bundle"]). When omitted, all tabs shown. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
|
-
import { Tabs, TabsList, TabsTrigger } from "../../../ui";
|
|
4
|
+
import { EmptyState, Tabs, TabsList, TabsTrigger } from "../../../ui";
|
|
5
5
|
import { CategoryProductsListing } from "./CategoryProductsListing";
|
|
6
6
|
import { AuctionsIndexListing } from "../../products/components/AuctionsIndexListing";
|
|
7
7
|
import { PreOrdersIndexListing } from "../../pre-orders/components/PreOrdersIndexListing";
|
|
@@ -37,16 +37,7 @@ const GENERIC_TAB_LISTING_TYPES = {
|
|
|
37
37
|
[pluginFor("art").tabSlug]: ART_STICKERS_LISTING_TYPES,
|
|
38
38
|
};
|
|
39
39
|
export function BrandDetailTabs({ brandName, initialProductsData, initialBundles = [], counts, enabledListingTypes, enabledCategoryTypes, }) {
|
|
40
|
-
const countFor = (id) =>
|
|
41
|
-
switch (id) {
|
|
42
|
-
case "products": return counts?.products;
|
|
43
|
-
case "auctions": return counts?.auctions;
|
|
44
|
-
case "pre-orders": return counts?.preOrders;
|
|
45
|
-
case "prize-draws": return counts?.prizeDraws;
|
|
46
|
-
case "bundles": return counts?.bundles;
|
|
47
|
-
default: return undefined;
|
|
48
|
-
}
|
|
49
|
-
};
|
|
40
|
+
const countFor = (id) => counts?.[id];
|
|
50
41
|
const visibleTabs = CATEGORY_PAGE_TABS.filter((t) => {
|
|
51
42
|
const mapping = TAB_TYPE_MAP[t.id];
|
|
52
43
|
// A tab with no mapping has no render branch below and would show a blank
|
|
@@ -63,11 +54,16 @@ export function BrandDetailTabs({ brandName, initialProductsData, initialBundles
|
|
|
63
54
|
if (!enabledCategoryTypes.includes(mapping.type))
|
|
64
55
|
return false;
|
|
65
56
|
}
|
|
66
|
-
// Hide a tab only when its count is known and explicitly zero.
|
|
57
|
+
// Hide a tab only when its count is known and explicitly zero. `undefined`
|
|
58
|
+
// now means the count query FAILED (every tab is counted), so the tab stays
|
|
59
|
+
// visible rather than hiding real listings behind a swallowed error.
|
|
67
60
|
const count = countFor(t.id);
|
|
68
61
|
return count === undefined || count > 0;
|
|
69
62
|
});
|
|
70
63
|
const firstTabId = (visibleTabs[0]?.id ?? "products");
|
|
71
64
|
const [activeTab, setActiveTab] = useState(firstTabId);
|
|
65
|
+
if (visibleTabs.length === 0) {
|
|
66
|
+
return (_jsx(EmptyState, { title: "Nothing listed here yet", description: "This brand has no active listings right now. Check back soon." }));
|
|
67
|
+
}
|
|
72
68
|
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 }))] }));
|
|
73
69
|
}
|
|
@@ -43,7 +43,7 @@ export function BundleCollage({ members, onItemClick }) {
|
|
|
43
43
|
return (_jsxs(_Fragment, { children: [_jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-2 sm:grid-cols-3 md:grid-cols-4", children: members.map((p, idx) => {
|
|
44
44
|
const cover = p.mainImage ?? p.images?.[0];
|
|
45
45
|
const href = String(ROUTES.PUBLIC.PRODUCT_DETAIL?.(p.slug ?? p.id) ?? "#");
|
|
46
|
-
return (_jsxs(Div, { className: "group relative overflow-hidden border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] transition-transform hover:scale-[1.01]", rounded: "lg", children: [
|
|
46
|
+
return (_jsxs(Div, { layout: "flex-col", className: "group relative overflow-hidden border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] transition-transform hover:scale-[1.01]", rounded: "lg", children: [_jsx(Button, { variant: "ghost", type: "button", onClick: makeBundleItemClickHandler(p, idx, onItemClick, setLightboxIndex), paddingX: "none", paddingY: "none", rounded: "none", className: "relative aspect-square w-full overflow-hidden focus-visible:ring-2 focus-visible:ring-[var(--appkit-color-primary)]", "aria-label": `View ${p.title} in lightbox`, children: _jsxs(Div, { className: "relative aspect-square w-full", children: [cover ? (_jsx(MediaImage, { src: cover, alt: p.title, size: "card", className: "transition-transform group-hover:scale-105" })) : (_jsx(Row, { textSize: "3xl", surface: "muted", className: "absolute inset-0", align: "center", justify: "center", children: PLACEHOLDER_EMOJI })), _jsxs(Div, { textWeight: "semibold", textSize: "xs", surface: "overlay-xl", padding: "chip-2xs", className: "absolute left-2 top-2 text-white", rounded: "default", children: ["#", idx + 1] })] }) }), _jsxs(Div, { padding: "xs", children: [_jsx(Link, { href: href, className: "line-clamp-2 text-[length:var(--appkit-text-sm)] font-medium hover:underline", children: p.title }), _jsx(Text, { className: "mt-0.5 text-[var(--appkit-color-text-muted)]", size: "xs", children: formatCurrency(p.price ?? 0, p.currency ?? "INR") })] })] }, p.id));
|
|
47
47
|
}) }), _jsx(ImageLightbox, { images: galleryImages, activeIndex: lightboxIndex, onClose: () => setLightboxIndex(null), onNavigate: (i) => setLightboxIndex(i), showThumbnails: true })] }));
|
|
48
48
|
}
|
|
49
49
|
export default BundleCollage;
|
|
@@ -11,6 +11,10 @@ import { CategoryGrid } from "./CategoryGrid";
|
|
|
11
11
|
import { CategoryHighlightsAndFaqSection } from "./CategoryHighlightsAndFaqSection";
|
|
12
12
|
import { GroupedListingsCarousel } from "../../grouped/components/GroupedListingsCarousel";
|
|
13
13
|
import { getGroupsForCategory } from "../../../_internal/server/features/grouped/data";
|
|
14
|
+
import { listingTabCounts, } from "../../../_internal/server/features/products/listing-tab-counts";
|
|
15
|
+
import { CATEGORY_PAGE_TABS } from "../../products/constants/listing-tabs";
|
|
16
|
+
import { enabledCategoryTypes, enabledListingTypes } from "../../../_internal/shared/listing-types/feature-flags";
|
|
17
|
+
import { siteSettingsRepository } from "../../../repositories";
|
|
14
18
|
const __O = {
|
|
15
19
|
hidden: "overflow-hidden",
|
|
16
20
|
xAuto: "overflow-x-auto",
|
|
@@ -19,16 +23,15 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
19
23
|
const category = await categoriesRepository
|
|
20
24
|
.getCategoryBySlug(slug)
|
|
21
25
|
.catch(() => undefined);
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
// array-contains
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
const [productsResult, auctionsCountResult, preOrdersCountResult, prizeDrawsCountResult, bundlesResult, childCategories, rootSiblingCategories, groupedListings] = await Promise.all([
|
|
26
|
+
// Products carry their FULL ancestor chain in `categorySlugs`, so matching on
|
|
27
|
+
// this category's own id already returns the whole subtree — no descendant
|
|
28
|
+
// expansion needed. Expanding it was also a latent outage: `categoriesIn` is
|
|
29
|
+
// applied as `array-contains-any`, which Firestore caps at 30 values, and
|
|
30
|
+
// every caller here wraps the query in `.catch(() => null)` — so a category
|
|
31
|
+
// tree deeper than a couple of levels would have turned into a silently blank
|
|
32
|
+
// page rather than an error (Root Cause #59).
|
|
33
|
+
const categoriesIn = category?.id ? [category.id] : null;
|
|
34
|
+
const [productsResult, tabCounts, bundlesResult, childCategories, rootSiblingCategories, groupedListings, settings] = await Promise.all([
|
|
32
35
|
categoriesIn
|
|
33
36
|
? productRepository
|
|
34
37
|
.list({
|
|
@@ -39,36 +42,12 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
39
42
|
}, { categoriesIn })
|
|
40
43
|
.catch(() => null)
|
|
41
44
|
: Promise.resolve(null),
|
|
45
|
+
// One count per tab, derived from CATEGORY_PAGE_TABS itself — so every
|
|
46
|
+
// listing type is counted and a type with nothing in this category hides.
|
|
47
|
+
// Four of the ten tabs used to have no count at all and could never hide.
|
|
42
48
|
categoriesIn
|
|
43
|
-
?
|
|
44
|
-
|
|
45
|
-
filters: sieveAnd(sieveFilter("status", SIEVE_OP.EQ, "published"), sieveFilter("listingType", SIEVE_OP.EQ, "auction")),
|
|
46
|
-
sorts: sortBy("auctionEndDate", "ASC"),
|
|
47
|
-
page: 1,
|
|
48
|
-
pageSize: 1,
|
|
49
|
-
}, { categoriesIn })
|
|
50
|
-
.catch(() => null)
|
|
51
|
-
: Promise.resolve(null),
|
|
52
|
-
categoriesIn
|
|
53
|
-
? productRepository
|
|
54
|
-
.list({
|
|
55
|
-
filters: sieveAnd(sieveFilter("status", SIEVE_OP.EQ, "published"), sieveFilter("listingType", SIEVE_OP.EQ, "pre-order")),
|
|
56
|
-
sorts: sortBy("createdAt", "DESC"),
|
|
57
|
-
page: 1,
|
|
58
|
-
pageSize: 1,
|
|
59
|
-
}, { categoriesIn })
|
|
60
|
-
.catch(() => null)
|
|
61
|
-
: Promise.resolve(null),
|
|
62
|
-
categoriesIn
|
|
63
|
-
? productRepository
|
|
64
|
-
.list({
|
|
65
|
-
filters: sieveAnd(sieveFilter("status", SIEVE_OP.EQ, "published"), sieveFilter("listingType", SIEVE_OP.EQ, "prize-draw")),
|
|
66
|
-
sorts: sortBy("createdAt", "DESC"),
|
|
67
|
-
page: 1,
|
|
68
|
-
pageSize: 1,
|
|
69
|
-
}, { categoriesIn })
|
|
70
|
-
.catch(() => null)
|
|
71
|
-
: Promise.resolve(null),
|
|
49
|
+
? listingTabCounts(CATEGORY_PAGE_TABS, { categoriesIn })
|
|
50
|
+
: Promise.resolve({}),
|
|
72
51
|
// SB-UNI-D — bundles fetched from the categories collection. We pull
|
|
73
52
|
// all active bundle rows; the carousel filters by category affinity.
|
|
74
53
|
categoriesIn
|
|
@@ -86,15 +65,34 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
86
65
|
? categoriesRepository.getCategoriesByRootId(category.rootId).catch(() => [])
|
|
87
66
|
: Promise.resolve([]),
|
|
88
67
|
category?.slug ? getGroupsForCategory(category.slug).catch(() => []) : Promise.resolve([]),
|
|
68
|
+
// Listing/category-type feature flags. This page accepted the props all
|
|
69
|
+
// along but never passed them, so an admin disabling a listing type still
|
|
70
|
+
// saw its tab here while the store page correctly hid it.
|
|
71
|
+
siteSettingsRepository.findById("global").catch(() => null),
|
|
89
72
|
]);
|
|
90
73
|
const relatedCategories = rootSiblingCategories.filter((c) => c.id !== category?.id && (!c.categoryType || c.categoryType === "category"));
|
|
91
|
-
// Stores tab —
|
|
74
|
+
// Stores tab — stores whose storeCategory is this category or ANY descendant.
|
|
75
|
+
//
|
|
76
|
+
// `storeCategory` is a single slug with no ancestor chain (unlike a product's
|
|
77
|
+
// categorySlugs), so the descendant list genuinely is needed here. It used to
|
|
78
|
+
// use direct children only, which meant a store filed under a tier-3 category
|
|
79
|
+
// was invisible on its root's Stores tab, and it issued one query per slug —
|
|
80
|
+
// an unbounded N+1. Both are fixed by pipe-joining the slugs into OR-groups:
|
|
81
|
+
// the enhanced Sieve adapter turns a same-field OR into a Firestore `in`,
|
|
82
|
+
// which caps at 30 values, hence the chunking.
|
|
92
83
|
const storeCategorySlugs = [
|
|
93
84
|
slug,
|
|
94
|
-
...
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
85
|
+
...(await categoriesRepository
|
|
86
|
+
.getDescendantIds(category?.id ?? "")
|
|
87
|
+
.catch(() => [])),
|
|
88
|
+
].filter(Boolean);
|
|
89
|
+
const SLUG_CHUNK = 30;
|
|
90
|
+
const slugChunks = [];
|
|
91
|
+
for (let i = 0; i < storeCategorySlugs.length; i += SLUG_CHUNK) {
|
|
92
|
+
slugChunks.push(storeCategorySlugs.slice(i, i + SLUG_CHUNK));
|
|
93
|
+
}
|
|
94
|
+
const storeResults = await Promise.all(slugChunks.map((chunk) => storeRepository
|
|
95
|
+
.listStores({ filters: sieveFilter("storeCategory", SIEVE_OP.EQ, chunk.join("|")), page: 1, pageSize: 50 }, true)
|
|
98
96
|
.catch(() => null)));
|
|
99
97
|
const seen = new Set();
|
|
100
98
|
const categoryStores = storeResults
|
|
@@ -122,22 +120,20 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
122
120
|
averageRating: s.stats?.averageRating,
|
|
123
121
|
createdAt: s.createdAt,
|
|
124
122
|
}));
|
|
125
|
-
const productCount = productsResult?.total ?? category?.metrics?.totalProductCount ?? category?.metrics?.productCount ?? 0;
|
|
126
|
-
const auctionCount = auctionsCountResult?.total ?? category?.metrics?.totalAuctionCount ?? category?.metrics?.auctionCount ?? 0;
|
|
127
|
-
const preOrderCount = preOrdersCountResult?.total ?? 0;
|
|
128
|
-
const prizeDrawCount = prizeDrawsCountResult?.total ?? 0;
|
|
129
|
-
const bundleCount = bundlesResult?.length ?? 0;
|
|
130
123
|
const storeCount = categoryStores.length;
|
|
124
|
+
// `tabCounts` is keyed by tabSlug and already covers every listing type plus
|
|
125
|
+
// bundles; `stores` is added here because this page resolves its store list
|
|
126
|
+
// eagerly to render the tab body anyway.
|
|
127
|
+
const counts = { ...tabCounts, stores: storeCount };
|
|
128
|
+
// Header pills read the same numbers the tabs do. `?? 0` is safe here (a pill
|
|
129
|
+
// simply doesn't render) — unlike the tab bar, where an unknown count must
|
|
130
|
+
// stay visible rather than collapse to "hide me".
|
|
131
|
+
const productCount = counts.products ?? category?.metrics?.totalProductCount ?? category?.metrics?.productCount ?? 0;
|
|
132
|
+
const auctionCount = counts.auctions ?? category?.metrics?.totalAuctionCount ?? category?.metrics?.auctionCount ?? 0;
|
|
133
|
+
const preOrderCount = counts["pre-orders"] ?? 0;
|
|
131
134
|
const coverImage = category?.display?.coverImage;
|
|
132
135
|
const hasCover = Boolean(coverImage);
|
|
133
136
|
return (_jsxs(Main, { children: [_jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-[var(--appkit-color-bg)]"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "absolute inset-0", children: _jsx(MediaImage, { src: coverImage, alt: "", size: "banner" }) }), _jsx(Div, { surface: "overlay-md", className: "absolute inset-0" })] })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsxs(Nav, { layout: "flex", gap: "2xs", textSize: "sm", className: "mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORIES), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 transition-colors", children: "Categories" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Span, { weight: "medium", className: hasCover ? "text-white" : "text-[var(--appkit-color-text)]", children: category?.name ?? slug })] }), _jsx(Heading, { color: "inverse", level: 1, className: `mb-2 ${hasCover ? "" : "text-[var(--appkit-color-text)]"}`, size: "3xl", mdSize: "4xl", weight: "bold", children: category?.name ?? slug }), category?.description && typeof category.description === "string" && !category.description.startsWith("{") && (_jsx(Text, { color: hasCover ? "inverse" : "muted", className: `max-w-2xl mb-4 ${hasCover ? "/80" : ""}`, size: "base", children: category.description })), _jsxs(Row, { wrap: true, gap: "sm", children: [productCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: "inverse", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400", rounded: "full", padding: "pill-sm-tall", children: [productCount.toLocaleString(), " ", productCount === 1 ? "product" : "products"] })), auctionCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "warning", surface: hasCover ? undefined : "warning-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [auctionCount.toLocaleString(), " ", auctionCount === 1 ? "auction" : "auctions"] })), preOrderCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "info", surface: hasCover ? undefined : "info-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [preOrderCount.toLocaleString(), " ", preOrderCount === 1 ? "pre-order" : "pre-orders"] })), storeCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "success", surface: hasCover ? undefined : "success-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [storeCount.toLocaleString(), " ", storeCount === 1 ? "store" : "stores"] }))] })] })] }), _jsx(CategoryHighlightsAndFaqSection, { highlights: category?.highlights, faqs: category?.faqs }), childCategories.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-b", children: _jsx(Div, { className: "max-w-7xl mx-auto", padding: "inline", children: _jsx(Div, { layout: "flex", gap: "2", className: `.5 ${__O.xAuto} [scrollbar-width:none]`, padding: "b-2xs", children: childCategories.map((child) => (_jsxs(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(child.slug)),
|
|
134
137
|
// audit-responsive-wrap-ok: deliberate horizontal-scroll chip row (parent has overflow-x-auto) — each chip staying single-line is the intended design, not hidden content
|
|
135
|
-
className: "flex-shrink-0 flex items-center gap-[var(--appkit-space-1-5)] rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-elevated)] px-[var(--appkit-space-4)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-text-muted)] hover:border-primary hover:text-primary transition-colors whitespace-nowrap", children: [child.display?.icon && (_jsx(Span, { className: "leading-none", children: child.display.icon })), child.name, (child.metrics?.productCount ?? 0) > 0 && (_jsx(Span, { size: "xs", color: "muted", children: (child.metrics?.productCount ?? 0).toLocaleString() }))] }, child.id))) }) }) })), _jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(CategoryDetailTabs, { categorySlug: slug, categoryId: category?.id, initialProductsData: productsResult ?? undefined, initialBundles: bundlesResult ?? [], initialStores: categoryStores, counts: {
|
|
136
|
-
products: productCount,
|
|
137
|
-
auctions: auctionCount,
|
|
138
|
-
preOrders: preOrderCount,
|
|
139
|
-
prizeDraws: prizeDrawCount,
|
|
140
|
-
bundles: bundleCount,
|
|
141
|
-
stores: storeCount,
|
|
142
|
-
} }) }) }), groupedListings.length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(GroupedListingsCarousel, { groups: groupedListings }) }) })), relatedCategories.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-t", padding: "y-lg", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 2, className: "mb-4", size: "xl", weight: "semibold", children: "Related Categories" }), _jsx(CategoryGrid, { categories: relatedCategories, getHref: (c) => String(ROUTES.PUBLIC.CATEGORY_DETAIL(c.slug)) })] }) }))] }));
|
|
138
|
+
className: "flex-shrink-0 flex items-center gap-[var(--appkit-space-1-5)] rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-elevated)] px-[var(--appkit-space-4)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-text-muted)] hover:border-primary hover:text-primary transition-colors whitespace-nowrap", children: [child.display?.icon && (_jsx(Span, { className: "leading-none", children: child.display.icon })), child.name, (child.metrics?.productCount ?? 0) > 0 && (_jsx(Span, { size: "xs", color: "muted", children: (child.metrics?.productCount ?? 0).toLocaleString() }))] }, child.id))) }) }) })), _jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(CategoryDetailTabs, { categorySlug: slug, categoryId: category?.id, initialProductsData: productsResult ?? undefined, initialBundles: bundlesResult ?? [], initialStores: categoryStores, counts: counts, enabledListingTypes: enabledListingTypes(settings), enabledCategoryTypes: enabledCategoryTypes(settings) }) }) }), groupedListings.length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(GroupedListingsCarousel, { groups: groupedListings }) }) })), relatedCategories.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-t", padding: "y-lg", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 2, className: "mb-4", size: "xl", weight: "semibold", children: "Related Categories" }), _jsx(CategoryGrid, { categories: relatedCategories, getHref: (c) => String(ROUTES.PUBLIC.CATEGORY_DETAIL(c.slug)) })] }) }))] }));
|
|
143
139
|
}
|
|
@@ -7,14 +7,16 @@ export interface CategoryDetailTabsProps {
|
|
|
7
7
|
initialProductsData?: any;
|
|
8
8
|
initialBundles?: CategoryDocument[];
|
|
9
9
|
initialStores?: StoreListItem[];
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
/**
|
|
11
|
+
* tabSlug -> row count. `undefined` means the count could not be resolved,
|
|
12
|
+
* NOT zero — such a tab stays visible so a failed query can never hide real
|
|
13
|
+
* inventory (Root Cause #59).
|
|
14
|
+
*
|
|
15
|
+
* Keyed by tab id rather than by hand-named properties: the previous shape
|
|
16
|
+
* named only 6 of the 10 tabs, so classifieds / digital-codes / live / art
|
|
17
|
+
* were structurally uncountable and could never hide (Root Cause #61).
|
|
18
|
+
*/
|
|
19
|
+
counts?: Record<string, number | undefined>;
|
|
18
20
|
/** Enabled listing types (e.g. ["standard","auction","pre-order"]). When omitted, all tabs shown. */
|
|
19
21
|
enabledListingTypes?: string[];
|
|
20
22
|
/** Enabled category types (e.g. ["category","brand","bundle"]). When omitted, all tabs shown. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
|
-
import { Tabs, TabsList, TabsTrigger } from "../../../ui";
|
|
4
|
+
import { EmptyState, Tabs, TabsList, TabsTrigger } from "../../../ui";
|
|
5
5
|
import { CategoryProductsListing } from "./CategoryProductsListing";
|
|
6
6
|
import { AuctionsIndexListing } from "../../products/components/AuctionsIndexListing";
|
|
7
7
|
import { PreOrdersIndexListing } from "../../pre-orders/components/PreOrdersIndexListing";
|
|
@@ -39,17 +39,7 @@ const GENERIC_TAB_LISTING_TYPES = {
|
|
|
39
39
|
[pluginFor("art").tabSlug]: ART_STICKERS_LISTING_TYPES,
|
|
40
40
|
};
|
|
41
41
|
export function CategoryDetailTabs({ categorySlug, categoryId, initialProductsData, initialBundles = [], initialStores = [], counts, enabledListingTypes, enabledCategoryTypes, }) {
|
|
42
|
-
const countFor = (id) =>
|
|
43
|
-
switch (id) {
|
|
44
|
-
case "products": return counts?.products;
|
|
45
|
-
case "auctions": return counts?.auctions;
|
|
46
|
-
case "pre-orders": return counts?.preOrders;
|
|
47
|
-
case "prize-draws": return counts?.prizeDraws;
|
|
48
|
-
case "bundles": return counts?.bundles;
|
|
49
|
-
case "stores": return counts?.stores;
|
|
50
|
-
default: return undefined;
|
|
51
|
-
}
|
|
52
|
-
};
|
|
42
|
+
const countFor = (id) => counts?.[id];
|
|
53
43
|
const visibleTabs = CATEGORY_PAGE_TABS.filter((t) => {
|
|
54
44
|
const mapping = TAB_TYPE_MAP[t.id];
|
|
55
45
|
if (mapping) {
|
|
@@ -62,14 +52,19 @@ export function CategoryDetailTabs({ categorySlug, categoryId, initialProductsDa
|
|
|
62
52
|
return false;
|
|
63
53
|
}
|
|
64
54
|
}
|
|
65
|
-
// Hide a tab only when its count is known and explicitly zero
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
55
|
+
// Hide a tab only when its count is known and explicitly zero. `undefined`
|
|
56
|
+
// now means the count query FAILED (every tab is counted), so keeping it
|
|
57
|
+
// visible is the fail-open choice: an empty tab is a far smaller error than
|
|
58
|
+
// hiding a tab that holds real listings.
|
|
69
59
|
const count = countFor(t.id);
|
|
70
60
|
return count === undefined || count > 0;
|
|
71
61
|
});
|
|
72
62
|
const firstTabId = (visibleTabs[0]?.id ?? "products");
|
|
73
63
|
const [activeTab, setActiveTab] = useState(firstTabId);
|
|
64
|
+
// Every tab counted zero — render the empty state rather than an empty tab
|
|
65
|
+
// strip with no body beneath it.
|
|
66
|
+
if (visibleTabs.length === 0) {
|
|
67
|
+
return (_jsx(EmptyState, { title: "Nothing listed here yet", description: "This category has no active listings right now. Check back soon." }));
|
|
68
|
+
}
|
|
74
69
|
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 }))] }));
|
|
75
70
|
}
|
|
@@ -13,6 +13,9 @@ import { ProductFilters, PRODUCT_PUBLIC_SORT_OPTIONS } from "../../products/comp
|
|
|
13
13
|
import { useGuestCart } from "../../cart/hooks/useGuestCart";
|
|
14
14
|
import { useGuestWishlist } from "../../wishlist/hooks/useGuestWishlist";
|
|
15
15
|
import { pushCartOp, pushWishlistOp } from "../../cart/utils/pending-ops";
|
|
16
|
+
import { AvailabilityTabs } from "../../products/components/AvailabilityTabs";
|
|
17
|
+
import { AVAILABILITY_VALUES } from "../../../constants/field-names";
|
|
18
|
+
import { TABLE_KEYS } from "../../../constants/table-keys";
|
|
16
19
|
const __P = {
|
|
17
20
|
p3: "p-[var(--appkit-space-3)]",
|
|
18
21
|
};
|
|
@@ -38,6 +41,12 @@ export function CategoryProductsListing({ categorySlug, categoryId, brandName, l
|
|
|
38
41
|
const localWishlist = useGuestWishlist();
|
|
39
42
|
const wishlistedIds = new Set(localWishlist.items.filter((i) => i.type === "product").map((i) => i.itemId));
|
|
40
43
|
const { pendingTable, filterActiveCount, onFilterApply, onFilterClear, onResetAll, onFilterReset } = usePendingTable(table, FILTER_KEYS);
|
|
44
|
+
// Absent means "available" — the same default `listStoreProducts`
|
|
45
|
+
// resolves server-side. These six store tabs sent NO availability
|
|
46
|
+
// param at all until 2026-08-24, while their SSR fetch did apply one,
|
|
47
|
+
// so any client refetch silently widened the list (Root Cause #30).
|
|
48
|
+
const availability = table.get(TABLE_KEYS.AVAILABILITY) ||
|
|
49
|
+
AVAILABILITY_VALUES.AVAILABLE;
|
|
41
50
|
const params = {
|
|
42
51
|
q: table.get("q") || undefined,
|
|
43
52
|
category: categoryId || undefined,
|
|
@@ -52,6 +61,7 @@ export function CategoryProductsListing({ categorySlug, categoryId, brandName, l
|
|
|
52
61
|
// sievejs parses it as a same-field OR and the Firebase adapter upgrades
|
|
53
62
|
// it to a `.where(…, "in", …)` query.
|
|
54
63
|
listingType: listingTypes.join("|"),
|
|
64
|
+
availability,
|
|
55
65
|
};
|
|
56
66
|
const { products, totalPages, page, isLoading } = useProducts(params, { initialData });
|
|
57
67
|
const commitSearch = useCallback(() => {
|
|
@@ -106,7 +116,7 @@ export function CategoryProductsListing({ categorySlug, categoryId, brandName, l
|
|
|
106
116
|
}, [localCart, showToast]);
|
|
107
117
|
return (_jsxs(Div, { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: `Search in ${categorySlug.replace(/-/g, " ")}...`, onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || "-createdAt", sortOptions: PRODUCT_PUBLIC_SORT_OPTIONS, onSortChange: (v) => {
|
|
108
118
|
table.set("sort", v);
|
|
109
|
-
}, view: view === "card" ? "grid" : "list", onViewChange: (v) => handleViewToggle(v === "grid" ? "card" : "list"), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), _jsx(Div, { padding: "y-lg", children: renderCategoryProductGrid({
|
|
119
|
+
}, view: view === "card" ? "grid" : "list", onViewChange: (v) => handleViewToggle(v === "grid" ? "card" : "list"), onResetAll: resetAll, hasActiveState: hasActiveState }), _jsx(Div, { padding: "y-sm", children: _jsx(AvailabilityTabs, { types: listingTypes }) }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), _jsx(Div, { padding: "y-lg", children: renderCategoryProductGrid({
|
|
110
120
|
isLoading, products: products, view,
|
|
111
121
|
getProductHref: (p) => String(ROUTES.PUBLIC.PRODUCT_DETAIL(p.slug || p.id)),
|
|
112
122
|
onWishlistToggle: handleWishlistToggle, wishlistedIds, onAddToCart: handleAddToCart,
|
|
@@ -2,5 +2,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Button, Div, Heading, Text } from "../../../ui";
|
|
3
3
|
import { MediaImage } from "../../media/MediaImage";
|
|
4
4
|
export function ConcernCard({ concern, onClick, className = "", }) {
|
|
5
|
-
return (_jsxs(Button, { type: "button", variant: "outline", onClick: () => onClick?.(concern),
|
|
5
|
+
return (_jsxs(Button, { type: "button", variant: "outline", onClick: () => onClick?.(concern),
|
|
6
|
+
// No bare `flex` here: globals.css declares `.flex` un-layered + !important,
|
|
7
|
+
// which overrides `.appkit-button`'s own display. It is redundant anyway —
|
|
8
|
+
// the button is already a flex container. `flex-col items-center` now
|
|
9
|
+
// actually reaches the icon and label, which have been rendering
|
|
10
|
+
// side-by-side since the ripple wrapper landed (Root Cause #68).
|
|
11
|
+
className: `group flex-col items-center gap-[var(--appkit-space-3)] rounded-xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] p-[var(--appkit-space-4)] text-left shadow-sm transition hover:border-primary dark:hover:border-primary-400 hover:shadow-md ${className}`, children: [concern.display?.coverImage && (_jsx(Div, { className: "relative h-16 w-16", rounded: "full", overflow: "hidden", children: _jsx(MediaImage, { src: concern.display.coverImage, alt: concern.name, size: "thumbnail" }) })), _jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "text-[var(--appkit-color-text)] group-hover:text-primary dark:group-hover:text-primary-400", size: "sm", weight: "semibold", children: concern.name }), concern.description && (_jsx(Text, { className: `mt-1`, color: "muted", truncate: 2, size: "xs", children: concern.description }))] })] }));
|
|
6
12
|
}
|
|
@@ -162,6 +162,19 @@ export interface CategoryDocument extends BaseDocument {
|
|
|
162
162
|
bundleQueryResolvedAt?: Date;
|
|
163
163
|
/** Hand-picked products list (mirror of bundleQueryRule for static rules); kept for index-friendly queries. */
|
|
164
164
|
bundleProductIds?: string[];
|
|
165
|
+
/**
|
|
166
|
+
* Categories this bundle belongs to — the union of its member products'
|
|
167
|
+
* `categorySlugs`. A bundle row has no category of its own, so without this
|
|
168
|
+
* the category page could not scope its Bundles tab at all and counted every
|
|
169
|
+
* active bundle site-wide.
|
|
170
|
+
*
|
|
171
|
+
* Denormalised at bundle create/update time and refreshed by the daily bundle
|
|
172
|
+
* stock sync. Per Root Cause #42 a mirror is never trusted alone: readers
|
|
173
|
+
* treat an ABSENT mirror as "unscoped" (show it) rather than "belongs to no
|
|
174
|
+
* category" (hide it), so a write path that forgets to set it degrades to
|
|
175
|
+
* over-inclusion instead of silently losing the bundle.
|
|
176
|
+
*/
|
|
177
|
+
bundleCategorySlugs?: string[];
|
|
165
178
|
/**
|
|
166
179
|
* Sum of member products' individual prices (decimal rupees) — the "buy
|
|
167
180
|
* separately" total the discount badge is measured against. Denormalised
|